summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java31
-rw-r--r--apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java6
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobScheduler.java12
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java40
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java85
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java17
-rw-r--r--core/java/android/app/ActivityThread.java41
-rw-r--r--core/java/android/app/ApplicationLoaders.java8
-rw-r--r--core/java/android/companion/virtual/IVirtualDevice.aidl108
-rw-r--r--core/java/android/companion/virtual/IVirtualDeviceManager.aidl2
-rw-r--r--core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl2
-rw-r--r--core/java/android/companion/virtual/VirtualDevice.java5
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceInternal.java458
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceManager.java508
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceParams.java15
-rw-r--r--core/java/android/companion/virtual/audio/AudioCapture.java10
-rw-r--r--core/java/android/companion/virtual/audio/AudioInjection.java10
-rw-r--r--core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl4
-rw-r--r--core/java/android/companion/virtual/sensor/VirtualSensor.java2
-rw-r--r--core/java/android/companion/virtual/sensor/VirtualSensorCallback.java4
-rw-r--r--core/java/android/companion/virtual/sensor/VirtualSensorConfig.java34
-rw-r--r--core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelCallback.java4
-rw-r--r--core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java35
-rw-r--r--core/java/android/companion/virtual/sensor/VirtualSensorEvent.java2
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java10
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java7
-rw-r--r--core/java/android/hardware/input/InputManager.java4
-rw-r--r--core/java/android/hardware/soundtrigger/OWNERS3
-rw-r--r--core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java1
-rw-r--r--core/java/android/net/vcn/VcnConfig.java7
-rw-r--r--core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java4
-rw-r--r--core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java11
-rw-r--r--core/java/android/provider/ContactsContract.java18
-rw-r--r--core/java/android/service/autofill/FillContext.java2
-rw-r--r--core/java/android/view/PointerIcon.java5
-rw-r--r--core/java/android/view/View.java70
-rw-r--r--core/java/android/view/autofill/AutofillClientController.java7
-rw-r--r--core/java/android/view/inputmethod/RemoteInputConnectionImpl.java16
-rw-r--r--core/java/android/widget/TextView.java5
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java47
-rw-r--r--core/jni/android_util_Binder.cpp11
-rw-r--r--core/res/res/values/config.xml19
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/coretests/src/android/app/ApplicationLoadersTest.java30
-rw-r--r--core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java12
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml31
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_floating_button.xml31
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml31
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_more_button.xml31
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_select_button.xml30
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml28
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml3
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_close.xml (renamed from libs/WindowManager/Shell/res/drawable/caption_collapse_menu_button.xml)20
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_desktop.xml26
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_floating.xml (renamed from libs/WindowManager/Shell/res/drawable/caption_close_button.xml)20
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_fullscreen.xml (renamed from libs/WindowManager/Shell/res/drawable/caption_screenshot_button.xml)20
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_screenshot.xml34
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml25
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_splitscreen.xml26
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml2
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml136
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml3
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_app_info_pill.xml57
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_more_actions_pill.xml47
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_windowing_pill.xml62
-rw-r--r--libs/WindowManager/Shell/res/values/colors.xml3
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml32
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values/styles.xml34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java108
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java74
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java239
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt41
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt116
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt89
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt86
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt36
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java2
-rw-r--r--location/java/android/location/Location.java4
-rw-r--r--media/OWNERS1
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/OWNERS3
-rw-r--r--media/java/android/media/AudioSystem.java5
-rw-r--r--media/java/android/media/soundtrigger/OWNERS1
-rw-r--r--packages/CompanionDeviceManager/res/values/strings.xml10
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java3
-rw-r--r--packages/CredentialManager/res/values/strings.xml2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt28
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java28
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java53
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java24
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java83
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java19
-rw-r--r--packages/SystemUI/res-keyguard/values/strings.xml116
-rw-r--r--packages/SystemUI/res/values/dimens.xml7
-rw-r--r--packages/SystemUI/res/values/strings.xml5
-rw-r--r--packages/SystemUI/res/values/styles.xml2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt493
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java600
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeHeightLogger.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt140
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt260
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt372
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt125
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java102
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/SubscriptionManagerProxy.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt22
-rw-r--r--packages/SystemUI/tests/res/drawable-nodpi/romainguy_rockaway.jpgbin0 -> 414841 bytes
-rw-r--r--packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java174
-rw-r--r--packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt77
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt77
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt346
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java53
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt133
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt173
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java38
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt42
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt39
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java3
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java24
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java22
-rw-r--r--services/core/java/com/android/server/VcnManagementService.java7
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java18
-rw-r--r--services/core/java/com/android/server/am/AppProfiler.java3
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueImpl.java6
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java5
-rw-r--r--services/core/java/com/android/server/am/CachedAppOptimizer.java158
-rw-r--r--services/core/java/com/android/server/am/EventLogTags.logtags2
-rw-r--r--services/core/java/com/android/server/am/NativeCrashListener.java135
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java6
-rw-r--r--services/core/java/com/android/server/am/ProcessErrorStateRecord.java27
-rw-r--r--services/core/java/com/android/server/am/StackTracesDumpHelper.java13
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java6
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java15
-rw-r--r--services/core/java/com/android/server/audio/BtHelper.java1
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssLocationProvider.java6
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java5
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceUtils.java4
-rw-r--r--services/core/java/com/android/server/pm/Settings.java8
-rw-r--r--services/core/java/com/android/server/powerstats/PowerStatsService.java2
-rw-r--r--services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java24
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java1
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java4
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java12
-rw-r--r--services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java2
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java9
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java2
-rw-r--r--services/core/java/com/android/server/wm/InsetsStateController.java21
-rw-r--r--services/core/java/com/android/server/wm/RectInsetsSourceProvider.java53
-rw-r--r--services/core/java/com/android/server/wm/Transition.java23
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java109
-rw-r--r--services/core/java/com/android/server/wm/WindowContainerInsetsSourceProvider.java34
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java32
-rw-r--r--services/credentials/java/com/android/server/credentials/ClearRequestSession.java2
-rw-r--r--services/credentials/java/com/android/server/credentials/CreateRequestSession.java2
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerService.java128
-rw-r--r--services/credentials/java/com/android/server/credentials/GetRequestSession.java7
-rw-r--r--services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java217
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderClearSession.java9
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderCreateSession.java12
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderGetSession.java53
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java3
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderSession.java14
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java82
-rw-r--r--services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java (renamed from services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java)10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java6
-rw-r--r--services/voiceinteraction/OWNERS1
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/OWNERS3
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt51
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml12
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java6
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeablePortraitActivity.java (renamed from packages/SystemUI/src/com/android/systemui/log/dagger/ShadeHeightLog.java)24
-rw-r--r--tests/SoundTriggerTestApp/OWNERS2
-rw-r--r--tests/SoundTriggerTests/OWNERS2
-rw-r--r--tests/utils/testutils/java/android/os/test/FakePermissionEnforcer.java64
-rw-r--r--tests/utils/testutils/java/android/os/test/OWNERS1
-rw-r--r--tests/vcn/java/android/net/vcn/VcnConfigTest.java32
-rw-r--r--tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java7
242 files changed, 6352 insertions, 2869 deletions
diff --git a/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java b/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java
index 0b351013d23a..cbd602f0de76 100644
--- a/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java
+++ b/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java
@@ -45,11 +45,13 @@ import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.adservices.data.adselection.CustomAudienceSignals;
-import com.android.adservices.service.adselection.AdDataArgument;
-import com.android.adservices.service.adselection.AdSelectionConfigArgument;
-import com.android.adservices.service.adselection.AdWithBidArgument;
-import com.android.adservices.service.adselection.CustomAudienceBiddingSignalsArgument;
-import com.android.adservices.service.adselection.CustomAudienceScoringSignalsArgument;
+import com.android.adservices.service.adselection.AdCounterKeyCopier;
+import com.android.adservices.service.adselection.AdCounterKeyCopierNoOpImpl;
+import com.android.adservices.service.adselection.AdDataArgumentUtil;
+import com.android.adservices.service.adselection.AdSelectionConfigArgumentUtil;
+import com.android.adservices.service.adselection.AdWithBidArgumentUtil;
+import com.android.adservices.service.adselection.CustomAudienceBiddingSignalsArgumentUtil;
+import com.android.adservices.service.adselection.CustomAudienceScoringSignalsArgumentUtil;
import com.android.adservices.service.js.IsolateSettings;
import com.android.adservices.service.js.JSScriptArgument;
import com.android.adservices.service.js.JSScriptArrayArgument;
@@ -106,6 +108,14 @@ public class JSScriptEnginePerfTests {
private static final Instant ACTIVATION_TIME = CLOCK.instant();
private static final Instant EXPIRATION_TIME = CLOCK.instant().plus(Duration.ofDays(1));
private static final AdSelectionSignals CONTEXTUAL_SIGNALS = AdSelectionSignals.EMPTY;
+ private static final AdCounterKeyCopier AD_COUNTER_KEY_COPIER_NO_OP =
+ new AdCounterKeyCopierNoOpImpl();
+
+ private final AdDataArgumentUtil mAdDataArgumentUtil =
+ new AdDataArgumentUtil(AD_COUNTER_KEY_COPIER_NO_OP);
+ private final AdWithBidArgumentUtil mAdWithBidArgumentUtil =
+ new AdWithBidArgumentUtil(mAdDataArgumentUtil);
+
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -437,7 +447,7 @@ public class JSScriptEnginePerfTests {
List<AdData> adDataList = getSampleAdDataList(numOfAds, "https://ads.example/");
ImmutableList.Builder<JSScriptArgument> adDataListArgument = new ImmutableList.Builder<>();
for (AdData adData : adDataList) {
- adDataListArgument.add(AdDataArgument.asScriptArgument("ignored", adData));
+ adDataListArgument.add(mAdDataArgumentUtil.asScriptArgument("ignored", adData));
}
AdSelectionSignals perBuyerSignals = generatePerBuyerSignals(numOfAds);
AdSelectionSignals auctionSignals = AdSelectionSignals.fromString("{\"auctionSignal1"
@@ -455,7 +465,7 @@ public class JSScriptEnginePerfTests {
.add(jsonArg("perBuyerSignals", perBuyerSignals))
.add(jsonArg("trustedBiddingSignals", trustedBiddingSignals))
.add(jsonArg("contextualSignals", CONTEXTUAL_SIGNALS))
- .add(CustomAudienceBiddingSignalsArgument.asScriptArgument(
+ .add(CustomAudienceBiddingSignalsArgumentUtil.asScriptArgument(
"customAudienceBiddingSignal", customAudienceSignals))
.build();
InputStream testJsInputStream = sContext.getAssets().open(
@@ -485,7 +495,8 @@ public class JSScriptEnginePerfTests {
ImmutableList.Builder<JSScriptArgument> adWithBidArrayArgument =
new ImmutableList.Builder<>();
for (AdWithBid adWithBid : adWithBidList) {
- adWithBidArrayArgument.add(AdWithBidArgument.asScriptArgument("adWithBid", adWithBid));
+ adWithBidArrayArgument.add(
+ mAdWithBidArgumentUtil.asScriptArgument("adWithBid", adWithBid));
}
AdTechIdentifier seller = AdTechIdentifier.fromString("www.example-ssp.com");
AdSelectionSignals sellerSignals = AdSelectionSignals.fromString("{\"signals\":[]}");
@@ -507,12 +518,12 @@ public class JSScriptEnginePerfTests {
ImmutableList<JSScriptArgument> args = ImmutableList.<JSScriptArgument>builder()
.add(arrayArg("adsWithBids", adWithBidArrayArgument.build()))
- .add(AdSelectionConfigArgument.asScriptArgument(adSelectionConfig,
+ .add(AdSelectionConfigArgumentUtil.asScriptArgument(adSelectionConfig,
"adSelectionConfig"))
.add(jsonArg("sellerSignals", sellerSignals))
.add(jsonArg("trustedScoringSignals", trustedScoringSignalsJson))
.add(jsonArg("contextualSignals", CONTEXTUAL_SIGNALS))
- .add(CustomAudienceScoringSignalsArgument.asScriptArgument(
+ .add(CustomAudienceScoringSignalsArgumentUtil.asScriptArgument(
"customAudienceScoringSignal", customAudienceSignals))
.build();
InputStream testJsInputStream = sContext.getAssets().open(
diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
index 776d913e56cb..3cfddc6d8e2b 100644
--- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
+++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
@@ -65,8 +65,12 @@ public class JobSchedulerImpl extends JobScheduler {
@NonNull
@Override
public JobScheduler forNamespace(@NonNull String namespace) {
+ namespace = sanitizeNamespace(namespace);
if (namespace == null) {
- throw new IllegalArgumentException("namespace cannot be null");
+ throw new NullPointerException("namespace cannot be null");
+ }
+ if (namespace.isEmpty()) {
+ throw new IllegalArgumentException("namespace cannot be empty");
}
return new JobSchedulerImpl(this, namespace);
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index b8847add0734..d59d430e0b78 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -270,6 +270,9 @@ public abstract class JobScheduler {
* otherwise. Attempting to update a job scheduled in another namespace will not be possible
* but will instead create or update the job inside the current namespace. A JobScheduler
* instance dedicated to a namespace must be used to schedule or update jobs in that namespace.
+ *
+ * <p class="note">Since leading and trailing whitespace can lead to hard-to-debug issues,
+ * they will be {@link String#trim() trimmed}. An empty String (after trimming) is not allowed.
* @see #getNamespace()
*/
@NonNull
@@ -287,6 +290,15 @@ public abstract class JobScheduler {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
+ /** @hide */
+ @Nullable
+ public static String sanitizeNamespace(@Nullable String namespace) {
+ if (namespace == null) {
+ return null;
+ }
+ return namespace.trim().intern();
+ }
+
/**
* 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
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 3fe83a64b5ec..70b06cb534e2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -233,7 +233,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
- @VisibleForTesting
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public static Clock sUptimeMillisClock = new MySimpleClock(ZoneOffset.UTC) {
@Override
public long millis() {
@@ -241,7 +241,6 @@ public class JobSchedulerService extends com.android.server.SystemService
}
};
- @VisibleForTesting
public static Clock sElapsedRealtimeClock = new MySimpleClock(ZoneOffset.UTC) {
@Override
public long millis() {
@@ -4024,6 +4023,18 @@ public class JobSchedulerService extends com.android.server.SystemService
return JobScheduler.RESULT_SUCCESS;
}
+ /** Returns a sanitized namespace if valid, or throws an exception if not. */
+ private String validateNamespace(@Nullable String namespace) {
+ namespace = JobScheduler.sanitizeNamespace(namespace);
+ if (namespace != null) {
+ if (namespace.isEmpty()) {
+ throw new IllegalArgumentException("namespace cannot be empty");
+ }
+ namespace = namespace.intern();
+ }
+ return namespace;
+ }
+
private int validateRunUserInitiatedJobsPermission(int uid, String packageName) {
final int state = getRunUserInitiatedJobsPermissionState(uid, packageName);
if (state == PermissionChecker.PERMISSION_HARD_DENIED) {
@@ -4071,9 +4082,7 @@ public class JobSchedulerService extends com.android.server.SystemService
return result;
}
- if (namespace != null) {
- namespace = namespace.intern();
- }
+ namespace = validateNamespace(namespace);
final long ident = Binder.clearCallingIdentity();
try {
@@ -4104,9 +4113,7 @@ public class JobSchedulerService extends com.android.server.SystemService
return result;
}
- if (namespace != null) {
- namespace = namespace.intern();
- }
+ namespace = validateNamespace(namespace);
final long ident = Binder.clearCallingIdentity();
try {
@@ -4145,9 +4152,7 @@ public class JobSchedulerService extends com.android.server.SystemService
return result;
}
- if (namespace != null) {
- namespace = namespace.intern();
- }
+ namespace = validateNamespace(namespace);
final long ident = Binder.clearCallingIdentity();
try {
@@ -4184,7 +4189,8 @@ public class JobSchedulerService extends com.android.server.SystemService
final long ident = Binder.clearCallingIdentity();
try {
return new ParceledListSlice<>(
- JobSchedulerService.this.getPendingJobsInNamespace(uid, namespace));
+ JobSchedulerService.this.getPendingJobsInNamespace(uid,
+ validateNamespace(namespace)));
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -4196,7 +4202,8 @@ public class JobSchedulerService extends com.android.server.SystemService
final long ident = Binder.clearCallingIdentity();
try {
- return JobSchedulerService.this.getPendingJob(uid, namespace, jobId);
+ return JobSchedulerService.this.getPendingJob(
+ uid, validateNamespace(namespace), jobId);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -4208,7 +4215,8 @@ public class JobSchedulerService extends com.android.server.SystemService
final long ident = Binder.clearCallingIdentity();
try {
- return JobSchedulerService.this.getPendingJobReason(uid, namespace, jobId);
+ return JobSchedulerService.this.getPendingJobReason(
+ uid, validateNamespace(namespace), jobId);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -4238,7 +4246,7 @@ public class JobSchedulerService extends com.android.server.SystemService
JobSchedulerService.this.cancelJobsForUid(uid,
// Documentation says only jobs scheduled BY the app will be cancelled
/* includeSourceApp */ false,
- /* namespaceOnly */ true, namespace,
+ /* namespaceOnly */ true, validateNamespace(namespace),
JobParameters.STOP_REASON_CANCELLED_BY_APP,
JobParameters.INTERNAL_STOP_REASON_CANCELED,
"cancelAllInNamespace() called by app, callingUid=" + uid);
@@ -4253,7 +4261,7 @@ public class JobSchedulerService extends com.android.server.SystemService
final long ident = Binder.clearCallingIdentity();
try {
- JobSchedulerService.this.cancelJob(uid, namespace, jobId, uid,
+ JobSchedulerService.this.cancelJob(uid, validateNamespace(namespace), jobId, uid,
JobParameters.STOP_REASON_CANCELLED_BY_APP);
} finally {
Binder.restoreCallingIdentity(ident);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 234a93c8d168..5d2c9261c537 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -33,7 +33,9 @@ import android.annotation.Nullable;
import android.app.job.JobInfo;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.os.Handler;
import android.os.Looper;
+import android.os.Message;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArraySet;
@@ -66,6 +68,11 @@ public final class FlexibilityController extends StateController {
| CONSTRAINT_CHARGING
| CONSTRAINT_IDLE;
+ /** List of flexible constraints a job can opt into. */
+ static final int OPTIONAL_FLEXIBLE_CONSTRAINTS = CONSTRAINT_BATTERY_NOT_LOW
+ | CONSTRAINT_CHARGING
+ | CONSTRAINT_IDLE;
+
/** List of all job flexible constraints whose satisfaction is job specific. */
private static final int JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS = CONSTRAINT_CONNECTIVITY;
@@ -76,6 +83,9 @@ public final class FlexibilityController extends StateController {
private static final int NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS =
Integer.bitCount(JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS);
+ static final int NUM_OPTIONAL_FLEXIBLE_CONSTRAINTS =
+ Integer.bitCount(OPTIONAL_FLEXIBLE_CONSTRAINTS);
+
static final int NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS =
Integer.bitCount(SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS);
@@ -130,6 +140,7 @@ public final class FlexibilityController extends StateController {
final FlexibilityAlarmQueue mFlexibilityAlarmQueue;
@VisibleForTesting
final FcConfig mFcConfig;
+ private final FcHandler mHandler;
@VisibleForTesting
final PrefetchController mPrefetchController;
@@ -174,9 +185,12 @@ public final class FlexibilityController extends StateController {
}
};
+ private static final int MSG_UPDATE_JOBS = 0;
+
public FlexibilityController(
JobSchedulerService service, PrefetchController prefetchController) {
super(service);
+ mHandler = new FcHandler(AppSchedulingModuleThread.get().getLooper());
mDeviceSupportsFlexConstraints = !mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_AUTOMOTIVE);
mFlexibilityEnabled &= mDeviceSupportsFlexConstraints;
@@ -238,15 +252,16 @@ public final class FlexibilityController extends StateController {
boolean isFlexibilitySatisfiedLocked(JobStatus js) {
return !mFlexibilityEnabled
|| mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP
- || mService.isCurrentlyRunningLocked(js)
|| getNumSatisfiedFlexibleConstraintsLocked(js)
- >= js.getNumRequiredFlexibleConstraints();
+ >= js.getNumRequiredFlexibleConstraints()
+ || mService.isCurrentlyRunningLocked(js);
}
@VisibleForTesting
@GuardedBy("mLock")
int getNumSatisfiedFlexibleConstraintsLocked(JobStatus js) {
return Integer.bitCount(mSatisfiedFlexibleConstraints & js.getPreferredConstraintFlags())
+ // Connectivity is job-specific, so must be handled separately.
+ (js.getHasAccessToUnmetered() ? 1 : 0);
}
@@ -267,33 +282,11 @@ public final class FlexibilityController extends StateController {
+ " constraint: " + constraint + " state: " + state);
}
- final int prevSatisfied = Integer.bitCount(mSatisfiedFlexibleConstraints);
mSatisfiedFlexibleConstraints =
(mSatisfiedFlexibleConstraints & ~constraint) | (state ? constraint : 0);
- final int curSatisfied = Integer.bitCount(mSatisfiedFlexibleConstraints);
-
- // Only the max of the number of required flexible constraints will need to be updated
- // The rest did not have a change in state and are still satisfied or unsatisfied.
- final int numConstraintsToUpdate = Math.max(curSatisfied, prevSatisfied);
-
- // In order to get the range of all potentially satisfied jobs, we start at the number
- // of satisfied system-wide constraints and iterate to the max number of potentially
- // satisfied constraints, determined by how many job-specific constraints exist.
- for (int j = 0; j <= NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS; j++) {
- final ArraySet<JobStatus> jobsByNumConstraints = mFlexibilityTracker
- .getJobsByNumRequiredConstraints(numConstraintsToUpdate + j);
-
- if (jobsByNumConstraints == null) {
- // If there are no more jobs to iterate through we can just return.
- return;
- }
-
- for (int i = 0; i < jobsByNumConstraints.size(); i++) {
- JobStatus js = jobsByNumConstraints.valueAt(i);
- js.setFlexibilityConstraintSatisfied(
- nowElapsed, isFlexibilitySatisfiedLocked(js));
- }
- }
+ // Push the job update to the handler to avoid blocking other controllers and
+ // potentially batch back-to-back controller state updates together.
+ mHandler.obtainMessage(MSG_UPDATE_JOBS).sendToTarget();
}
}
@@ -630,6 +623,44 @@ public final class FlexibilityController extends StateController {
}
}
+ private class FcHandler extends Handler {
+ FcHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_UPDATE_JOBS:
+ removeMessages(MSG_UPDATE_JOBS);
+
+ synchronized (mLock) {
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ final ArraySet<JobStatus> changedJobs = new ArraySet<>();
+
+ for (int o = 0; o <= NUM_OPTIONAL_FLEXIBLE_CONSTRAINTS; ++o) {
+ final ArraySet<JobStatus> jobsByNumConstraints = mFlexibilityTracker
+ .getJobsByNumRequiredConstraints(o);
+
+ if (jobsByNumConstraints != null) {
+ for (int i = 0; i < jobsByNumConstraints.size(); i++) {
+ final JobStatus js = jobsByNumConstraints.valueAt(i);
+ if (js.setFlexibilityConstraintSatisfied(
+ nowElapsed, isFlexibilitySatisfiedLocked(js))) {
+ changedJobs.add(js);
+ }
+ }
+ }
+ }
+ if (changedJobs.size() > 0) {
+ mStateChangedListener.onControllerStateChanged(changedJobs);
+ }
+ }
+ break;
+ }
+ }
+ }
+
@VisibleForTesting
class FcConfig {
private boolean mShouldReevaluateConstraints = false;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 26d6ba254f8a..7cc2f28a5664 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -1131,10 +1131,25 @@ public final class JobStatus {
*/
@JobInfo.Priority
public int getEffectivePriority() {
- final int rawPriority = job.getPriority();
+ final boolean isDemoted =
+ (getInternalFlags() & INTERNAL_FLAG_DEMOTED_BY_USER) != 0
+ || (job.isUserInitiated()
+ && (getInternalFlags() & INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ) != 0);
+ final int maxPriority;
+ if (isDemoted) {
+ // If the job was demoted for some reason, limit its priority to HIGH.
+ maxPriority = JobInfo.PRIORITY_HIGH;
+ } else {
+ maxPriority = JobInfo.PRIORITY_MAX;
+ }
+ final int rawPriority = Math.min(maxPriority, job.getPriority());
if (numFailures < 2) {
return rawPriority;
}
+ if (shouldTreatAsUserInitiatedJob()) {
+ // Don't drop priority of UI jobs.
+ return rawPriority;
+ }
// Slowly decay priority of jobs to prevent starvation of other jobs.
if (isRequestedExpeditedJob()) {
// EJs can't fall below HIGH priority.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 5bedc9d95237..7b4aeeca2855 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -360,9 +360,8 @@ public final class ActivityThread extends ClientTransactionHandler
/** The activities to be truly destroyed (not include relaunch). */
final Map<IBinder, ClientTransactionItem> mActivitiesToBeDestroyed =
Collections.synchronizedMap(new ArrayMap<IBinder, ClientTransactionItem>());
- // List of new activities (via ActivityRecord.nextIdle) that should
- // be reported when next we idle.
- ActivityClientRecord mNewActivities = null;
+ // List of new activities that should be reported when next we idle.
+ final ArrayList<ActivityClientRecord> mNewActivities = new ArrayList<>();
// Number of activities that are currently visible on-screen.
@UnsupportedAppUsage
int mNumVisibleActivities = 0;
@@ -563,7 +562,6 @@ public final class ActivityThread extends ClientTransactionHandler
private Configuration tmpConfig = new Configuration();
// Callback used for updating activity override config and camera compat control state.
ViewRootImpl.ActivityConfigCallback activityConfigCallback;
- ActivityClientRecord nextIdle;
// Indicates whether this activity is currently the topmost resumed one in the system.
// This holds the last reported value from server.
@@ -657,7 +655,6 @@ public final class ActivityThread extends ClientTransactionHandler
paused = false;
stopped = false;
hideForNow = false;
- nextIdle = null;
activityConfigCallback = new ViewRootImpl.ActivityConfigCallback() {
@Override
public void onConfigurationChanged(Configuration overrideConfig,
@@ -2483,29 +2480,22 @@ public final class ActivityThread extends ClientTransactionHandler
private class Idler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
- ActivityClientRecord a = mNewActivities;
boolean stopProfiling = false;
if (mBoundApplication != null && mProfiler.profileFd != null
&& mProfiler.autoStopProfiler) {
stopProfiling = true;
}
- if (a != null) {
- mNewActivities = null;
- final ActivityClient ac = ActivityClient.getInstance();
- ActivityClientRecord prev;
- do {
- if (localLOGV) Slog.v(
- TAG, "Reporting idle of " + a +
- " finished=" +
- (a.activity != null && a.activity.mFinished));
- if (a.activity != null && !a.activity.mFinished) {
- ac.activityIdle(a.token, a.createdConfig, stopProfiling);
- a.createdConfig = null;
- }
- prev = a;
- a = a.nextIdle;
- prev.nextIdle = null;
- } while (a != null);
+ final ActivityClient ac = ActivityClient.getInstance();
+ while (mNewActivities.size() > 0) {
+ final ActivityClientRecord a = mNewActivities.remove(0);
+ if (localLOGV) {
+ Slog.v(TAG, "Reporting idle of " + a + " finished="
+ + (a.activity != null && a.activity.mFinished));
+ }
+ if (a.activity != null && !a.activity.mFinished) {
+ ac.activityIdle(a.token, a.createdConfig, stopProfiling);
+ a.createdConfig = null;
+ }
}
if (stopProfiling) {
mProfiler.stopProfiling();
@@ -5107,8 +5097,7 @@ public final class ActivityThread extends ClientTransactionHandler
}
}
- r.nextIdle = mNewActivities;
- mNewActivities = r;
+ mNewActivities.add(r);
if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r);
Looper.myQueue().addIdleHandler(new Idler());
}
@@ -5709,6 +5698,7 @@ public final class ActivityThread extends ClientTransactionHandler
}
if (finishing) {
ActivityClient.getInstance().activityDestroyed(r.token);
+ mNewActivities.remove(r);
}
mSomeActivitiesChanged = true;
}
@@ -5929,7 +5919,6 @@ public final class ActivityThread extends ClientTransactionHandler
r.activity = null;
r.window = null;
r.hideForNow = false;
- r.nextIdle = null;
// Merge any pending results and pending intents; don't just replace them
if (pendingResults != null) {
if (r.pendingResults == null) {
diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java
index 53b16d3a8170..9cd99dca1ab1 100644
--- a/core/java/android/app/ApplicationLoaders.java
+++ b/core/java/android/app/ApplicationLoaders.java
@@ -157,15 +157,15 @@ public class ApplicationLoaders {
* All libraries in the closure of libraries to be loaded must be in libs. A library can
* only depend on libraries that come before it in the list.
*/
- public void createAndCacheNonBootclasspathSystemClassLoaders(SharedLibraryInfo[] libs) {
+ public void createAndCacheNonBootclasspathSystemClassLoaders(List<SharedLibraryInfo> libs) {
if (mSystemLibsCacheMap != null) {
throw new IllegalStateException("Already cached.");
}
- mSystemLibsCacheMap = new HashMap<String, CachedClassLoader>();
+ mSystemLibsCacheMap = new HashMap<>();
- for (SharedLibraryInfo lib : libs) {
- createAndCacheNonBootclasspathSystemClassLoader(lib);
+ for (int i = 0; i < libs.size(); i++) {
+ createAndCacheNonBootclasspathSystemClassLoader(libs.get(i));
}
}
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 12882a2f47c9..9efdf2831b9e 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -39,21 +39,22 @@ import android.hardware.input.VirtualNavigationTouchpadConfig;
import android.os.ResultReceiver;
/**
- * Interface for a virtual device.
+ * Interface for a virtual device for communication between the system server and the process of
+ * the owner of the virtual device.
*
* @hide
*/
interface IVirtualDevice {
/**
- * Returns the association ID for this virtual device.
+ * Returns the CDM association ID of this virtual device.
*
* @see AssociationInfo#getId()
*/
int getAssociationId();
/**
- * Returns the unique device ID for this virtual device.
+ * Returns the unique ID of this virtual device.
*/
int getDeviceId();
@@ -64,55 +65,99 @@ interface IVirtualDevice {
void close();
/**
- * Notifies of an audio session being started.
+ * Notifies that an audio session being started.
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
- void onAudioSessionStarting(
- int displayId,
- IAudioRoutingCallback routingCallback,
+ void onAudioSessionStarting(int displayId, IAudioRoutingCallback routingCallback,
IAudioConfigChangedCallback configChangedCallback);
+ /**
+ * Notifies that an audio session has ended.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
void onAudioSessionEnded();
+ /**
+ * Creates a new dpad and registers it with the input framework with the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
- void createVirtualDpad(
- in VirtualDpadConfig config,
- IBinder token);
+ void createVirtualDpad(in VirtualDpadConfig config, IBinder token);
+
+ /**
+ * Creates a new keyboard and registers it with the input framework with the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
- void createVirtualKeyboard(
- in VirtualKeyboardConfig config,
- IBinder token);
+ void createVirtualKeyboard(in VirtualKeyboardConfig config, IBinder token);
+
+ /**
+ * Creates a new mouse and registers it with the input framework with the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
- void createVirtualMouse(
- in VirtualMouseConfig config,
- IBinder token);
+ void createVirtualMouse(in VirtualMouseConfig config, IBinder token);
+
+ /**
+ * Creates a new touchscreen and registers it with the input framework with the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
- void createVirtualTouchscreen(
- in VirtualTouchscreenConfig config,
- IBinder token);
+ void createVirtualTouchscreen(in VirtualTouchscreenConfig config, IBinder token);
+
+ /**
+ * Creates a new navigation touchpad and registers it with the input framework with the given
+ * token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
- void createVirtualNavigationTouchpad(
- in VirtualNavigationTouchpadConfig config,
- IBinder token);
+ void createVirtualNavigationTouchpad(in VirtualNavigationTouchpadConfig config, IBinder token);
+
+ /**
+ * Removes the input device corresponding to the given token from the framework.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
void unregisterInputDevice(IBinder token);
+
+ /**
+ * Returns the ID of the device corresponding to the given token, as registered with the input
+ * framework.
+ */
int getInputDeviceId(IBinder token);
+
+ /**
+ * Injects a key event to the virtual dpad corresponding to the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean sendDpadKeyEvent(IBinder token, in VirtualKeyEvent event);
+
+ /**
+ * Injects a key event to the virtual keyboard corresponding to the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean sendKeyEvent(IBinder token, in VirtualKeyEvent event);
+
+ /**
+ * Injects a button event to the virtual mouse corresponding to the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean sendButtonEvent(IBinder token, in VirtualMouseButtonEvent event);
+
+ /**
+ * Injects a relative event to the virtual mouse corresponding to the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean sendRelativeEvent(IBinder token, in VirtualMouseRelativeEvent event);
+
+ /**
+ * Injects a scroll event to the virtual mouse corresponding to the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean sendScrollEvent(IBinder token, in VirtualMouseScrollEvent event);
+
+ /**
+ * Injects a touch event to the virtual touch input device corresponding to the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event);
/**
- * Returns all virtual sensors for this device.
+ * Returns all virtual sensors created for this device.
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
List<VirtualSensor> getVirtualSensorList();
@@ -126,8 +171,13 @@ interface IVirtualDevice {
/**
* Launches a pending intent on the given display that is owned by this virtual device.
*/
- void launchPendingIntent(
- int displayId, in PendingIntent pendingIntent, in ResultReceiver resultReceiver);
+ void launchPendingIntent(int displayId, in PendingIntent pendingIntent,
+ in ResultReceiver resultReceiver);
+
+ /**
+ * Returns the current cursor position of the mouse corresponding to the given token, in x and y
+ * coordinates.
+ */
PointF getCursorPosition(IBinder token);
/** Sets whether to show or hide the cursor while this virtual device is active. */
@@ -140,8 +190,12 @@ interface IVirtualDevice {
* intent.
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
- void registerIntentInterceptor(
- in IVirtualDeviceIntentInterceptor intentInterceptor, in IntentFilter filter);
+ void registerIntentInterceptor(in IVirtualDeviceIntentInterceptor intentInterceptor,
+ in IntentFilter filter);
+
+ /**
+ * Unregisters a previously registered intent interceptor.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
void unregisterIntentInterceptor(in IVirtualDeviceIntentInterceptor intentInterceptor);
}
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 4f49b8dbd0dc..07743cef5889 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -101,7 +101,7 @@ interface IVirtualDeviceManager {
*
* @param deviceId id of the virtual device.
* @param sound effect type corresponding to
- * {@code android.media.AudioManager.SystemSoundEffect}
+ * {@code android.media.AudioManager.SystemSoundEffect}
*/
void playSoundEffect(int deviceId, int effectType);
}
diff --git a/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl
index 91c209fa098e..f28455477c50 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl
@@ -28,7 +28,7 @@ oneway interface IVirtualDeviceSoundEffectListener {
* Called when there's sound effect to be played on Virtual Device.
*
* @param sound effect type corresponding to
- * {@code android.media.AudioManager.SystemSoundEffect}
+ * {@code android.media.AudioManager.SystemSoundEffect}
*/
void onPlaySoundEffect(int effectType);
}
diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java
index 4a09186570e0..4ee65e077382 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -26,6 +26,11 @@ import java.util.Objects;
/**
* Details of a particular virtual device.
+ *
+ * <p>Read-only device representation exposing the properties of an existing virtual device.
+ *
+ * <p class="note">Not to be confused with {@link VirtualDeviceManager.VirtualDevice}, which is used
+ * by the virtual device creator and allows them to manage the device.
*/
public final class VirtualDevice implements Parcelable {
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
new file mode 100644
index 000000000000..045e4c6c77b1
--- /dev/null
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.PendingIntent;
+import android.companion.virtual.audio.VirtualAudioDevice;
+import android.companion.virtual.sensor.VirtualSensor;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.display.IVirtualDisplayCallback;
+import android.hardware.display.VirtualDisplay;
+import android.hardware.display.VirtualDisplayConfig;
+import android.hardware.input.VirtualDpad;
+import android.hardware.input.VirtualDpadConfig;
+import android.hardware.input.VirtualKeyboard;
+import android.hardware.input.VirtualKeyboardConfig;
+import android.hardware.input.VirtualMouse;
+import android.hardware.input.VirtualMouseConfig;
+import android.hardware.input.VirtualNavigationTouchpad;
+import android.hardware.input.VirtualNavigationTouchpadConfig;
+import android.hardware.input.VirtualTouchscreen;
+import android.hardware.input.VirtualTouchscreenConfig;
+import android.media.AudioManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.IntConsumer;
+
+/**
+ * An internal representation of a virtual device.
+ *
+ * @hide
+ */
+public class VirtualDeviceInternal {
+
+ private final Context mContext;
+ private final IVirtualDeviceManager mService;
+ private final IVirtualDevice mVirtualDevice;
+ private final Object mActivityListenersLock = new Object();
+ @GuardedBy("mActivityListenersLock")
+ private final ArrayMap<VirtualDeviceManager.ActivityListener, ActivityListenerDelegate>
+ mActivityListeners =
+ new ArrayMap<>();
+ private final Object mIntentInterceptorListenersLock = new Object();
+ @GuardedBy("mIntentInterceptorListenersLock")
+ private final ArrayMap<VirtualDeviceManager.IntentInterceptorCallback,
+ IntentInterceptorDelegate> mIntentInterceptorListeners =
+ new ArrayMap<>();
+ private final Object mSoundEffectListenersLock = new Object();
+ @GuardedBy("mSoundEffectListenersLock")
+ private final ArrayMap<VirtualDeviceManager.SoundEffectListener, SoundEffectListenerDelegate>
+ mSoundEffectListeners = new ArrayMap<>();
+ private final IVirtualDeviceActivityListener mActivityListenerBinder =
+ new IVirtualDeviceActivityListener.Stub() {
+
+ @Override
+ public void onTopActivityChanged(int displayId, ComponentName topActivity,
+ @UserIdInt int userId) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mActivityListenersLock) {
+ for (int i = 0; i < mActivityListeners.size(); i++) {
+ mActivityListeners.valueAt(i)
+ .onTopActivityChanged(displayId, topActivity);
+ mActivityListeners.valueAt(i)
+ .onTopActivityChanged(displayId, topActivity, userId);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void onDisplayEmpty(int displayId) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mActivityListenersLock) {
+ for (int i = 0; i < mActivityListeners.size(); i++) {
+ mActivityListeners.valueAt(i).onDisplayEmpty(displayId);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ };
+ private final IVirtualDeviceSoundEffectListener mSoundEffectListener =
+ new IVirtualDeviceSoundEffectListener.Stub() {
+ @Override
+ public void onPlaySoundEffect(int soundEffect) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSoundEffectListenersLock) {
+ for (int i = 0; i < mSoundEffectListeners.size(); i++) {
+ mSoundEffectListeners.valueAt(i).onPlaySoundEffect(soundEffect);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ };
+ @Nullable
+ private VirtualAudioDevice mVirtualAudioDevice;
+
+ VirtualDeviceInternal(
+ IVirtualDeviceManager service,
+ Context context,
+ int associationId,
+ VirtualDeviceParams params) throws RemoteException {
+ mService = service;
+ mContext = context.getApplicationContext();
+ mVirtualDevice = service.createVirtualDevice(
+ new Binder(),
+ mContext.getPackageName(),
+ associationId,
+ params,
+ mActivityListenerBinder,
+ mSoundEffectListener);
+ }
+
+ int getDeviceId() {
+ try {
+ return mVirtualDevice.getDeviceId();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @NonNull Context createContext() {
+ try {
+ return mContext.createDeviceContext(mVirtualDevice.getDeviceId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @NonNull
+ List<VirtualSensor> getVirtualSensorList() {
+ try {
+ return mVirtualDevice.getVirtualSensorList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void launchPendingIntent(
+ int displayId,
+ @NonNull PendingIntent pendingIntent,
+ @NonNull Executor executor,
+ @NonNull IntConsumer listener) {
+ try {
+ mVirtualDevice.launchPendingIntent(
+ displayId,
+ pendingIntent,
+ new ResultReceiver(new Handler(Looper.getMainLooper())) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ super.onReceiveResult(resultCode, resultData);
+ executor.execute(() -> listener.accept(resultCode));
+ }
+ });
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ @Nullable
+ VirtualDisplay createVirtualDisplay(
+ @NonNull VirtualDisplayConfig config,
+ @Nullable @CallbackExecutor Executor executor,
+ @Nullable VirtualDisplay.Callback callback) {
+ IVirtualDisplayCallback callbackWrapper =
+ new DisplayManagerGlobal.VirtualDisplayCallback(callback, executor);
+ final int displayId;
+ try {
+ displayId = mService.createVirtualDisplay(config, callbackWrapper, mVirtualDevice,
+ mContext.getPackageName());
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ DisplayManagerGlobal displayManager = DisplayManagerGlobal.getInstance();
+ return displayManager.createVirtualDisplayWrapper(config, callbackWrapper,
+ displayId);
+ }
+
+ void close() {
+ try {
+ // This also takes care of unregistering all virtual sensors.
+ mVirtualDevice.close();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ if (mVirtualAudioDevice != null) {
+ mVirtualAudioDevice.close();
+ mVirtualAudioDevice = null;
+ }
+ }
+
+ @NonNull
+ VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) {
+ try {
+ final IBinder token = new Binder(
+ "android.hardware.input.VirtualDpad:" + config.getInputDeviceName());
+ mVirtualDevice.createVirtualDpad(config, token);
+ return new VirtualDpad(mVirtualDevice, token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @NonNull
+ VirtualKeyboard createVirtualKeyboard(@NonNull VirtualKeyboardConfig config) {
+ try {
+ final IBinder token = new Binder(
+ "android.hardware.input.VirtualKeyboard:" + config.getInputDeviceName());
+ mVirtualDevice.createVirtualKeyboard(config, token);
+ return new VirtualKeyboard(mVirtualDevice, token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @NonNull
+ VirtualMouse createVirtualMouse(@NonNull VirtualMouseConfig config) {
+ try {
+ final IBinder token = new Binder(
+ "android.hardware.input.VirtualMouse:" + config.getInputDeviceName());
+ mVirtualDevice.createVirtualMouse(config, token);
+ return new VirtualMouse(mVirtualDevice, token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @NonNull
+ VirtualTouchscreen createVirtualTouchscreen(
+ @NonNull VirtualTouchscreenConfig config) {
+ try {
+ final IBinder token = new Binder(
+ "android.hardware.input.VirtualTouchscreen:" + config.getInputDeviceName());
+ mVirtualDevice.createVirtualTouchscreen(config, token);
+ return new VirtualTouchscreen(mVirtualDevice, token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @NonNull
+ VirtualNavigationTouchpad createVirtualNavigationTouchpad(
+ @NonNull VirtualNavigationTouchpadConfig config) {
+ try {
+ final IBinder token = new Binder(
+ "android.hardware.input.VirtualNavigationTouchpad:"
+ + config.getInputDeviceName());
+ mVirtualDevice.createVirtualNavigationTouchpad(config, token);
+ return new VirtualNavigationTouchpad(mVirtualDevice, token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @NonNull
+ VirtualAudioDevice createVirtualAudioDevice(
+ @NonNull VirtualDisplay display,
+ @Nullable Executor executor,
+ @Nullable VirtualAudioDevice.AudioConfigurationChangeCallback callback) {
+ if (mVirtualAudioDevice == null) {
+ mVirtualAudioDevice = new VirtualAudioDevice(mContext, mVirtualDevice, display,
+ executor, callback, () -> mVirtualAudioDevice = null);
+ }
+ return mVirtualAudioDevice;
+ }
+
+ @NonNull
+ void setShowPointerIcon(boolean showPointerIcon) {
+ try {
+ mVirtualDevice.setShowPointerIcon(showPointerIcon);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void addActivityListener(
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull VirtualDeviceManager.ActivityListener listener) {
+ final ActivityListenerDelegate delegate = new ActivityListenerDelegate(
+ Objects.requireNonNull(listener), Objects.requireNonNull(executor));
+ synchronized (mActivityListenersLock) {
+ mActivityListeners.put(listener, delegate);
+ }
+ }
+
+ void removeActivityListener(@NonNull VirtualDeviceManager.ActivityListener listener) {
+ synchronized (mActivityListenersLock) {
+ mActivityListeners.remove(Objects.requireNonNull(listener));
+ }
+ }
+
+ void addSoundEffectListener(@CallbackExecutor @NonNull Executor executor,
+ @NonNull VirtualDeviceManager.SoundEffectListener soundEffectListener) {
+ final SoundEffectListenerDelegate delegate =
+ new SoundEffectListenerDelegate(Objects.requireNonNull(executor),
+ Objects.requireNonNull(soundEffectListener));
+ synchronized (mSoundEffectListenersLock) {
+ mSoundEffectListeners.put(soundEffectListener, delegate);
+ }
+ }
+
+ void removeSoundEffectListener(
+ @NonNull VirtualDeviceManager.SoundEffectListener soundEffectListener) {
+ synchronized (mSoundEffectListenersLock) {
+ mSoundEffectListeners.remove(Objects.requireNonNull(soundEffectListener));
+ }
+ }
+
+ void registerIntentInterceptor(
+ @NonNull IntentFilter interceptorFilter,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull VirtualDeviceManager.IntentInterceptorCallback interceptorCallback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(interceptorFilter);
+ Objects.requireNonNull(interceptorCallback);
+ final IntentInterceptorDelegate delegate =
+ new IntentInterceptorDelegate(executor, interceptorCallback);
+ try {
+ mVirtualDevice.registerIntentInterceptor(delegate, interceptorFilter);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ synchronized (mIntentInterceptorListenersLock) {
+ mIntentInterceptorListeners.put(interceptorCallback, delegate);
+ }
+ }
+
+ void unregisterIntentInterceptor(
+ @NonNull VirtualDeviceManager.IntentInterceptorCallback interceptorCallback) {
+ Objects.requireNonNull(interceptorCallback);
+ final IntentInterceptorDelegate delegate;
+ synchronized (mIntentInterceptorListenersLock) {
+ delegate = mIntentInterceptorListeners.remove(interceptorCallback);
+ }
+ if (delegate != null) {
+ try {
+ mVirtualDevice.unregisterIntentInterceptor(delegate);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * A wrapper for {@link VirtualDeviceManager.ActivityListener} that executes callbacks on the
+ * given executor.
+ */
+ private static class ActivityListenerDelegate {
+ @NonNull private final VirtualDeviceManager.ActivityListener mActivityListener;
+ @NonNull private final Executor mExecutor;
+
+ ActivityListenerDelegate(@NonNull VirtualDeviceManager.ActivityListener listener,
+ @NonNull Executor executor) {
+ mActivityListener = listener;
+ mExecutor = executor;
+ }
+
+ public void onTopActivityChanged(int displayId, ComponentName topActivity) {
+ mExecutor.execute(() -> mActivityListener.onTopActivityChanged(displayId, topActivity));
+ }
+
+ public void onTopActivityChanged(int displayId, ComponentName topActivity,
+ @UserIdInt int userId) {
+ mExecutor.execute(() ->
+ mActivityListener.onTopActivityChanged(displayId, topActivity, userId));
+ }
+
+ public void onDisplayEmpty(int displayId) {
+ mExecutor.execute(() -> mActivityListener.onDisplayEmpty(displayId));
+ }
+ }
+
+ /**
+ * A wrapper for {@link VirtualDeviceManager.IntentInterceptorCallback} that executes callbacks
+ * on the given executor.
+ */
+ private static class IntentInterceptorDelegate extends IVirtualDeviceIntentInterceptor.Stub {
+ @NonNull private final VirtualDeviceManager.IntentInterceptorCallback
+ mIntentInterceptorCallback;
+ @NonNull private final Executor mExecutor;
+
+ private IntentInterceptorDelegate(Executor executor,
+ VirtualDeviceManager.IntentInterceptorCallback interceptorCallback) {
+ mExecutor = executor;
+ mIntentInterceptorCallback = interceptorCallback;
+ }
+
+ @Override
+ public void onIntentIntercepted(Intent intent) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mIntentInterceptorCallback.onIntentIntercepted(intent));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ /**
+ * A wrapper for {@link VirtualDeviceManager.SoundEffectListener} that executes callbacks on the
+ * given executor.
+ */
+ private static class SoundEffectListenerDelegate {
+ @NonNull private final VirtualDeviceManager.SoundEffectListener mSoundEffectListener;
+ @NonNull private final Executor mExecutor;
+
+ private SoundEffectListenerDelegate(Executor executor,
+ VirtualDeviceManager.SoundEffectListener soundEffectCallback) {
+ mSoundEffectListener = soundEffectCallback;
+ mExecutor = executor;
+ }
+
+ public void onPlaySoundEffect(@AudioManager.SystemSoundEffect int effectType) {
+ mExecutor.execute(() -> mSoundEffectListener.onPlaySoundEffect(effectType));
+ }
+ }
+}
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index cb9f06c39393..da6784be4404 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -40,8 +40,6 @@ import android.content.IntentFilter;
import android.graphics.Point;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.VirtualDisplayFlag;
-import android.hardware.display.DisplayManagerGlobal;
-import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplay;
import android.hardware.display.VirtualDisplayConfig;
import android.hardware.input.VirtualDpad;
@@ -55,31 +53,28 @@ import android.hardware.input.VirtualNavigationTouchpadConfig;
import android.hardware.input.VirtualTouchscreen;
import android.hardware.input.VirtualTouchscreenConfig;
import android.media.AudioManager;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.util.ArrayMap;
import android.util.Log;
import android.view.Surface;
-import com.android.internal.annotations.GuardedBy;
-
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.IntConsumer;
/**
- * System level service for managing virtual devices.
+ * System level service for creation and management of virtual devices.
+ *
+ * <p>VirtualDeviceManager enables interactive sharing of capabilities between the host Android
+ * device and a remote device.
+ *
+ * <p class="note">Not to be confused with the Android Studio's Virtual Device Manager, which allows
+ * for device emulation.
*/
@SystemService(Context.VIRTUAL_DEVICE_SERVICE)
public final class VirtualDeviceManager {
@@ -185,6 +180,9 @@ public final class VirtualDeviceManager {
/**
* Returns the details of all available virtual devices.
+ *
+ * <p>The returned objects are read-only representations that expose the properties of all
+ * existing virtual devices.
*/
@NonNull
public List<android.companion.virtual.VirtualDevice> getVirtualDevices() {
@@ -263,11 +261,12 @@ public final class VirtualDeviceManager {
*
* @param deviceId - id of the virtual audio device
* @return Device specific session id to be used for audio playback (see
- * {@link android.media.AudioManager.generateAudioSessionId}) if virtual device has
- * {@link VirtualDeviceParams.POLICY_TYPE_AUDIO} set to
- * {@link VirtualDeviceParams.DEVICE_POLICY_CUSTOM} and Virtual Audio Device
- * is configured in context-aware mode.
- * Otherwise {@link AUDIO_SESSION_ID_GENERATE} constant is returned.
+ * {@link AudioManager#generateAudioSessionId}) if virtual device has
+ * {@link VirtualDeviceParams#POLICY_TYPE_AUDIO} set to
+ * {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM} and Virtual Audio Device
+ * is configured in context-aware mode. Otherwise
+ * {@link AudioManager#AUDIO_SESSION_ID_GENERATE} constant is returned.
+ *
* @hide
*/
public int getAudioPlaybackSessionId(int deviceId) {
@@ -286,11 +285,12 @@ public final class VirtualDeviceManager {
*
* @param deviceId - id of the virtual audio device
* @return Device specific session id to be used for audio recording (see
- * {@link android.media.AudioManager.generateAudioSessionId}) if virtual device has
- * {@link VirtualDeviceParams.POLICY_TYPE_AUDIO} set to
- * {@link VirtualDeviceParams.DEVICE_POLICY_CUSTOM} and Virtual Audio Device
- * is configured in context-aware mode.
- * Otherwise {@link AUDIO_SESSION_ID_GENERATE} constant is returned.
+ * {@link AudioManager#generateAudioSessionId}) if virtual device has
+ * {@link VirtualDeviceParams#POLICY_TYPE_AUDIO} set to
+ * {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM} and Virtual Audio Device
+ * is configured in context-aware mode. Otherwise
+ * {@link AudioManager#AUDIO_SESSION_ID_GENERATE} constant is returned.
+ *
* @hide
*/
public int getAudioRecordingSessionId(int deviceId) {
@@ -307,10 +307,11 @@ public final class VirtualDeviceManager {
/**
* Requests sound effect to be played on virtual device.
*
- * @see android.media.AudioManager#playSoundEffect(int)
+ * @see AudioManager#playSoundEffect(int)
*
* @param deviceId - id of the virtual audio device
* @param effectType the type of sound effect
+ *
* @hide
*/
public void playSoundEffect(int deviceId, @AudioManager.SystemSoundEffect int effectType) {
@@ -326,86 +327,25 @@ public final class VirtualDeviceManager {
}
/**
- * A virtual device has its own virtual display, audio output, microphone, sensors, etc. The
- * creator of a virtual device can take the output from the virtual display and stream it over
- * to another device, and inject input events that are received from the remote device.
+ * A representation of a virtual device.
+ *
+ * <p>A virtual device can have its own virtual displays, audio input/output, sensors, etc.
+ * The creator of a virtual device can take the output from the virtual display and stream it
+ * over to another device, and inject input and sensor events that are received from the remote
+ * device.
+ *
+ * <p>This object is only used by the virtual device creator and allows them to manage the
+ * device's behavior, peripherals, and the user interaction with that device.
*
- * TODO(b/204081582): Consider using a builder pattern for the input APIs.
+ * <p class="note">Not to be confused with {@link android.companion.virtual.VirtualDevice},
+ * which is a read-only representation exposing the properties of an existing virtual device.
*
* @hide
*/
@SystemApi
public static class VirtualDevice implements AutoCloseable {
- private final Context mContext;
- private final IVirtualDeviceManager mService;
- private final IVirtualDevice mVirtualDevice;
- private final Object mActivityListenersLock = new Object();
- @GuardedBy("mActivityListenersLock")
- private final ArrayMap<ActivityListener, ActivityListenerDelegate> mActivityListeners =
- new ArrayMap<>();
- private final Object mIntentInterceptorListenersLock = new Object();
- @GuardedBy("mIntentInterceptorListenersLock")
- private final ArrayMap<IntentInterceptorCallback,
- VirtualIntentInterceptorDelegate> mIntentInterceptorListeners =
- new ArrayMap<>();
- private final Object mSoundEffectListenersLock = new Object();
- @GuardedBy("mSoundEffectListenersLock")
- private final ArrayMap<SoundEffectListener, SoundEffectListenerDelegate>
- mSoundEffectListeners = new ArrayMap<>();
- private final IVirtualDeviceActivityListener mActivityListenerBinder =
- new IVirtualDeviceActivityListener.Stub() {
-
- @Override
- public void onTopActivityChanged(int displayId, ComponentName topActivity,
- @UserIdInt int userId) {
- final long token = Binder.clearCallingIdentity();
- try {
- synchronized (mActivityListenersLock) {
- for (int i = 0; i < mActivityListeners.size(); i++) {
- mActivityListeners.valueAt(i)
- .onTopActivityChanged(displayId, topActivity);
- mActivityListeners.valueAt(i)
- .onTopActivityChanged(displayId, topActivity, userId);
- }
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void onDisplayEmpty(int displayId) {
- final long token = Binder.clearCallingIdentity();
- try {
- synchronized (mActivityListenersLock) {
- for (int i = 0; i < mActivityListeners.size(); i++) {
- mActivityListeners.valueAt(i).onDisplayEmpty(displayId);
- }
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- };
- private final IVirtualDeviceSoundEffectListener mSoundEffectListener =
- new IVirtualDeviceSoundEffectListener.Stub() {
- @Override
- public void onPlaySoundEffect(int soundEffect) {
- final long token = Binder.clearCallingIdentity();
- try {
- synchronized (mSoundEffectListenersLock) {
- for (int i = 0; i < mSoundEffectListeners.size(); i++) {
- mSoundEffectListeners.valueAt(i).onPlaySoundEffect(soundEffect);
- }
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- };
- @Nullable
- private VirtualAudioDevice mVirtualAudioDevice;
+ private final VirtualDeviceInternal mVirtualDeviceInternal;
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
private VirtualDevice(
@@ -413,38 +353,25 @@ public final class VirtualDeviceManager {
Context context,
int associationId,
VirtualDeviceParams params) throws RemoteException {
- mService = service;
- mContext = context.getApplicationContext();
- mVirtualDevice = service.createVirtualDevice(
- new Binder(),
- mContext.getPackageName(),
- associationId,
- params,
- mActivityListenerBinder,
- mSoundEffectListener);
+ mVirtualDeviceInternal =
+ new VirtualDeviceInternal(service, context, associationId, params);
}
/**
* Returns the unique ID of this virtual device.
*/
public int getDeviceId() {
- try {
- return mVirtualDevice.getDeviceId();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return mVirtualDeviceInternal.getDeviceId();
}
/**
- * @return A new Context bound to this device. This is a convenience method equivalent to
- * calling {@link Context#createDeviceContext(int)} with the device id of this device.
+ * Returns a new context bound to this device.
+ *
+ * <p>This is a convenience method equivalent to calling
+ * {@link Context#createDeviceContext(int)} with the id of this device.
*/
public @NonNull Context createContext() {
- try {
- return mContext.createDeviceContext(mVirtualDevice.getDeviceId());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return mVirtualDeviceInternal.createContext();
}
/**
@@ -456,11 +383,7 @@ public final class VirtualDeviceManager {
*/
@NonNull
public List<VirtualSensor> getVirtualSensorList() {
- try {
- return mVirtualDevice.getVirtualSensorList();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return mVirtualDeviceInternal.getVirtualSensorList();
}
/**
@@ -486,20 +409,8 @@ public final class VirtualDeviceManager {
@NonNull PendingIntent pendingIntent,
@NonNull Executor executor,
@NonNull IntConsumer listener) {
- try {
- mVirtualDevice.launchPendingIntent(
- displayId,
- pendingIntent,
- new ResultReceiver(new Handler(Looper.getMainLooper())) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- super.onReceiveResult(resultCode, resultData);
- executor.execute(() -> listener.accept(resultCode));
- }
- });
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
+ mVirtualDeviceInternal.launchPendingIntent(
+ displayId, pendingIntent, executor, listener);
}
/**
@@ -510,20 +421,19 @@ public final class VirtualDeviceManager {
* @param height The height of the virtual display in pixels, must be greater than 0.
* @param densityDpi The density of the virtual display in dpi, must be greater than 0.
* @param surface The surface to which the content of the virtual display should
- * be rendered, or null if there is none initially. The surface can also be set later using
- * {@link VirtualDisplay#setSurface(Surface)}.
+ * be rendered, or null if there is none initially. The surface can also be set later
+ * using {@link VirtualDisplay#setSurface(Surface)}.
* @param flags A combination of virtual display flags accepted by
- * {@link DisplayManager#createVirtualDisplay}. In addition, the following flags are
- * automatically set for all virtual devices:
- * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC VIRTUAL_DISPLAY_FLAG_PUBLIC} and
- * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
- * VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}.
+ * {@link DisplayManager#createVirtualDisplay}. In addition, the following flags are
+ * automatically set for all virtual devices:
+ * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC} and
+ * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}.
* @param executor The executor on which {@code callback} will be invoked. This is ignored
- * if {@code callback} is {@code null}. If {@code callback} is specified, this executor must
- * not be null.
+ * if {@code callback} is {@code null}. If {@code callback} is specified, this executor
+ * must not be null.
* @param callback Callback to call when the state of the {@link VirtualDisplay} changes
* @return The newly created virtual display, or {@code null} if the application could
- * not create the virtual display.
+ * not create the virtual display.
*
* @see DisplayManager#createVirtualDisplay
*
@@ -540,7 +450,7 @@ public final class VirtualDeviceManager {
@VirtualDisplayFlag int flags,
@Nullable @CallbackExecutor Executor executor,
@Nullable VirtualDisplay.Callback callback) {
- // Currently this just use the device ID, which means all of the virtual displays
+ // Currently this just uses the device ID, which means all of the virtual displays
// created using the same virtual device will have the same name if they use this
// deprecated API. The name should only be used for informational purposes, and not for
// identifying the display in code.
@@ -551,7 +461,7 @@ public final class VirtualDeviceManager {
if (surface != null) {
builder.setSurface(surface);
}
- return createVirtualDisplay(builder.build(), executor, callback);
+ return mVirtualDeviceInternal.createVirtualDisplay(builder.build(), executor, callback);
}
/**
@@ -560,11 +470,11 @@ public final class VirtualDeviceManager {
*
* @param config The configuration of the display.
* @param executor The executor on which {@code callback} will be invoked. This is ignored
- * if {@code callback} is {@code null}. If {@code callback} is specified, this executor must
- * not be null.
+ * if {@code callback} is {@code null}. If {@code callback} is specified, this executor
+ * must not be null.
* @param callback Callback to call when the state of the {@link VirtualDisplay} changes
* @return The newly created virtual display, or {@code null} if the application could
- * not create the virtual display.
+ * not create the virtual display.
*
* @see DisplayManager#createVirtualDisplay
*/
@@ -573,18 +483,7 @@ public final class VirtualDeviceManager {
@NonNull VirtualDisplayConfig config,
@Nullable @CallbackExecutor Executor executor,
@Nullable VirtualDisplay.Callback callback) {
- IVirtualDisplayCallback callbackWrapper =
- new DisplayManagerGlobal.VirtualDisplayCallback(callback, executor);
- final int displayId;
- try {
- displayId = mService.createVirtualDisplay(config, callbackWrapper, mVirtualDevice,
- mContext.getPackageName());
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- DisplayManagerGlobal displayManager = DisplayManagerGlobal.getInstance();
- return displayManager.createVirtualDisplayWrapper(config, callbackWrapper,
- displayId);
+ return mVirtualDeviceInternal.createVirtualDisplay(config, executor, callback);
}
/**
@@ -593,34 +492,18 @@ public final class VirtualDeviceManager {
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void close() {
- try {
- // This also takes care of unregistering all virtual sensors.
- mVirtualDevice.close();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- if (mVirtualAudioDevice != null) {
- mVirtualAudioDevice.close();
- mVirtualAudioDevice = null;
- }
+ mVirtualDeviceInternal.close();
}
/**
* Creates a virtual dpad.
*
- * @param config the configurations of the virtual Dpad.
+ * @param config the configurations of the virtual dpad.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
public VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) {
- try {
- final IBinder token = new Binder(
- "android.hardware.input.VirtualDpad:" + config.getInputDeviceName());
- mVirtualDevice.createVirtualDpad(config, token);
- return new VirtualDpad(mVirtualDevice, token);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return mVirtualDeviceInternal.createVirtualDpad(config);
}
/**
@@ -631,24 +514,16 @@ public final class VirtualDeviceManager {
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
public VirtualKeyboard createVirtualKeyboard(@NonNull VirtualKeyboardConfig config) {
- try {
- final IBinder token = new Binder(
- "android.hardware.input.VirtualKeyboard:" + config.getInputDeviceName());
- mVirtualDevice.createVirtualKeyboard(config, token);
- return new VirtualKeyboard(mVirtualDevice, token);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return mVirtualDeviceInternal.createVirtualKeyboard(config);
}
/**
* Creates a virtual keyboard.
*
- * @param display the display that the events inputted through this device should
- * target
- * @param inputDeviceName the name to call this input device
- * @param vendorId the PCI vendor id
- * @param productId the product id, as defined by the vendor
+ * @param display the display that the events inputted through this device should target.
+ * @param inputDeviceName the name of this keyboard device.
+ * @param vendorId the PCI vendor id.
+ * @param productId the product id, as defined by the vendor.
* @see #createVirtualKeyboard(VirtualKeyboardConfig config)
* @deprecated Use {@link #createVirtualKeyboard(VirtualKeyboardConfig config)} instead
*/
@@ -664,7 +539,7 @@ public final class VirtualDeviceManager {
.setInputDeviceName(inputDeviceName)
.setAssociatedDisplayId(display.getDisplay().getDisplayId())
.build();
- return createVirtualKeyboard(keyboardConfig);
+ return mVirtualDeviceInternal.createVirtualKeyboard(keyboardConfig);
}
/**
@@ -675,27 +550,18 @@ public final class VirtualDeviceManager {
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
public VirtualMouse createVirtualMouse(@NonNull VirtualMouseConfig config) {
- try {
- final IBinder token = new Binder(
- "android.hardware.input.VirtualMouse:" + config.getInputDeviceName());
- mVirtualDevice.createVirtualMouse(config, token);
- return new VirtualMouse(mVirtualDevice, token);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return mVirtualDeviceInternal.createVirtualMouse(config);
}
/**
* Creates a virtual mouse.
*
- * @param display the display that the events inputted through this device should
- * target
- * @param inputDeviceName the name to call this input device
- * @param vendorId the PCI vendor id
- * @param productId the product id, as defined by the vendor
+ * @param display the display that the events inputted through this device should target.
+ * @param inputDeviceName the name of this mouse.
+ * @param vendorId the PCI vendor id.
+ * @param productId the product id, as defined by the vendor.
* @see #createVirtualMouse(VirtualMouseConfig config)
* @deprecated Use {@link #createVirtualMouse(VirtualMouseConfig config)} instead
- * *
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@@ -709,7 +575,7 @@ public final class VirtualDeviceManager {
.setInputDeviceName(inputDeviceName)
.setAssociatedDisplayId(display.getDisplay().getDisplayId())
.build();
- return createVirtualMouse(mouseConfig);
+ return mVirtualDeviceInternal.createVirtualMouse(mouseConfig);
}
/**
@@ -721,48 +587,16 @@ public final class VirtualDeviceManager {
@NonNull
public VirtualTouchscreen createVirtualTouchscreen(
@NonNull VirtualTouchscreenConfig config) {
- try {
- final IBinder token = new Binder(
- "android.hardware.input.VirtualTouchscreen:" + config.getInputDeviceName());
- mVirtualDevice.createVirtualTouchscreen(config, token);
- return new VirtualTouchscreen(mVirtualDevice, token);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Creates a virtual touchpad in navigation mode.
- *
- * A touchpad in navigation mode means that its events are interpreted as navigation events
- * (up, down, etc) instead of using them to update a cursor's absolute position. If the
- * events are not consumed they are converted to DPAD events.
- *
- * @param config the configurations of the virtual navigation touchpad.
- */
- @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
- @NonNull
- public VirtualNavigationTouchpad createVirtualNavigationTouchpad(
- @NonNull VirtualNavigationTouchpadConfig config) {
- try {
- final IBinder token = new Binder(
- "android.hardware.input.VirtualNavigationTouchpad:"
- + config.getInputDeviceName());
- mVirtualDevice.createVirtualNavigationTouchpad(config, token);
- return new VirtualNavigationTouchpad(mVirtualDevice, token);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return mVirtualDeviceInternal.createVirtualTouchscreen(config);
}
/**
* Creates a virtual touchscreen.
*
- * @param display the display that the events inputted through this device should
- * target
- * @param inputDeviceName the name to call this input device
- * @param vendorId the PCI vendor id
- * @param productId the product id, as defined by the vendor
+ * @param display the display that the events inputted through this device should target.
+ * @param inputDeviceName the name of this touchscreen device.
+ * @param vendorId the PCI vendor id.
+ * @param productId the product id, as defined by the vendor.
* @see #createVirtualTouchscreen(VirtualTouchscreenConfig config)
* @deprecated Use {@link #createVirtualTouchscreen(VirtualTouchscreenConfig config)}
* instead
@@ -781,7 +615,25 @@ public final class VirtualDeviceManager {
.setInputDeviceName(inputDeviceName)
.setAssociatedDisplayId(display.getDisplay().getDisplayId())
.build();
- return createVirtualTouchscreen(touchscreenConfig);
+ return mVirtualDeviceInternal.createVirtualTouchscreen(touchscreenConfig);
+ }
+
+ /**
+ * Creates a virtual touchpad in navigation mode.
+ *
+ * <p>A touchpad in navigation mode means that its events are interpreted as navigation
+ * events (up, down, etc) instead of using them to update a cursor's absolute position. If
+ * the events are not consumed they are converted to DPAD events and delivered to the target
+ * again.
+ *
+ * @param config the configurations of the virtual navigation touchpad.
+ * @see android.view.InputDevice#SOURCE_TOUCH_NAVIGATION
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @NonNull
+ public VirtualNavigationTouchpad createVirtualNavigationTouchpad(
+ @NonNull VirtualNavigationTouchpadConfig config) {
+ return mVirtualDeviceInternal.createVirtualNavigationTouchpad(config);
}
/**
@@ -795,10 +647,10 @@ public final class VirtualDeviceManager {
*
* @param display The target virtual display to capture from and inject into.
* @param executor The {@link Executor} object for the thread on which to execute
- * the callback. If <code>null</code>, the {@link Executor} associated with
- * the main {@link Looper} will be used.
+ * the callback. If <code>null</code>, the {@link Executor} associated with the main
+ * {@link Looper} will be used.
* @param callback Interface to be notified when playback or recording configuration of
- * applications running on virtual display is changed.
+ * applications running on virtual display is changed.
* @return A {@link VirtualAudioDevice} instance.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@@ -807,27 +659,19 @@ public final class VirtualDeviceManager {
@NonNull VirtualDisplay display,
@Nullable Executor executor,
@Nullable AudioConfigurationChangeCallback callback) {
- if (mVirtualAudioDevice == null) {
- mVirtualAudioDevice = new VirtualAudioDevice(mContext, mVirtualDevice, display,
- executor, callback, () -> mVirtualAudioDevice = null);
- }
- return mVirtualAudioDevice;
+ return mVirtualDeviceInternal.createVirtualAudioDevice(display, executor, callback);
}
/**
* Sets the visibility of the pointer icon for this VirtualDevice's associated displays.
*
* @param showPointerIcon True if the pointer should be shown; false otherwise. The default
- * visibility is true.
+ * visibility is true.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
public void setShowPointerIcon(boolean showPointerIcon) {
- try {
- mVirtualDevice.setShowPointerIcon(showPointerIcon);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ mVirtualDeviceInternal.setShowPointerIcon(showPointerIcon);
}
/**
@@ -840,24 +684,17 @@ public final class VirtualDeviceManager {
*/
public void addActivityListener(
@CallbackExecutor @NonNull Executor executor, @NonNull ActivityListener listener) {
- final ActivityListenerDelegate delegate = new ActivityListenerDelegate(
- Objects.requireNonNull(listener), Objects.requireNonNull(executor));
- synchronized (mActivityListenersLock) {
- mActivityListeners.put(listener, delegate);
- }
+ mVirtualDeviceInternal.addActivityListener(executor, listener);
}
/**
- * Removes an activity listener previously added with
- * {@link #addActivityListener}.
+ * Removes an activity listener previously added with {@link #addActivityListener}.
*
* @param listener The listener to remove.
* @see #addActivityListener(Executor, ActivityListener)
*/
public void removeActivityListener(@NonNull ActivityListener listener) {
- synchronized (mActivityListenersLock) {
- mActivityListeners.remove(Objects.requireNonNull(listener));
- }
+ mVirtualDeviceInternal.removeActivityListener(listener);
}
/**
@@ -869,24 +706,17 @@ public final class VirtualDeviceManager {
*/
public void addSoundEffectListener(@CallbackExecutor @NonNull Executor executor,
@NonNull SoundEffectListener soundEffectListener) {
- final SoundEffectListenerDelegate delegate =
- new SoundEffectListenerDelegate(Objects.requireNonNull(executor),
- Objects.requireNonNull(soundEffectListener));
- synchronized (mSoundEffectListenersLock) {
- mSoundEffectListeners.put(soundEffectListener, delegate);
- }
+ mVirtualDeviceInternal.addSoundEffectListener(executor, soundEffectListener);
}
/**
- * Removes a sound effect listener previously added with {@link #addActivityListener}.
+ * Removes a sound effect listener previously added with {@link #addSoundEffectListener}.
*
* @param soundEffectListener The listener to remove.
- * @see #addActivityListener(Executor, ActivityListener)
+ * @see #addSoundEffectListener(Executor, SoundEffectListener)
*/
public void removeSoundEffectListener(@NonNull SoundEffectListener soundEffectListener) {
- synchronized (mSoundEffectListenersLock) {
- mSoundEffectListeners.remove(Objects.requireNonNull(soundEffectListener));
- }
+ mVirtualDeviceInternal.removeSoundEffectListener(soundEffectListener);
}
/**
@@ -905,40 +735,18 @@ public final class VirtualDeviceManager {
@NonNull IntentFilter interceptorFilter,
@CallbackExecutor @NonNull Executor executor,
@NonNull IntentInterceptorCallback interceptorCallback) {
- Objects.requireNonNull(executor);
- Objects.requireNonNull(interceptorFilter);
- Objects.requireNonNull(interceptorCallback);
- final VirtualIntentInterceptorDelegate delegate =
- new VirtualIntentInterceptorDelegate(executor, interceptorCallback);
- try {
- mVirtualDevice.registerIntentInterceptor(delegate, interceptorFilter);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- synchronized (mIntentInterceptorListenersLock) {
- mIntentInterceptorListeners.put(interceptorCallback, delegate);
- }
+ mVirtualDeviceInternal.registerIntentInterceptor(
+ interceptorFilter, executor, interceptorCallback);
}
/**
- * Unregisters the intent interceptorCallback previously registered with
+ * Unregisters the intent interceptor previously registered with
* {@link #registerIntentInterceptor}.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void unregisterIntentInterceptor(
@NonNull IntentInterceptorCallback interceptorCallback) {
- Objects.requireNonNull(interceptorCallback);
- final VirtualIntentInterceptorDelegate delegate;
- synchronized (mIntentInterceptorListenersLock) {
- delegate = mIntentInterceptorListeners.remove(interceptorCallback);
- }
- if (delegate != null) {
- try {
- mVirtualDevice.unregisterIntentInterceptor(delegate);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
+ mVirtualDeviceInternal.unregisterIntentInterceptor(interceptorCallback);
}
}
@@ -970,9 +778,9 @@ public final class VirtualDeviceManager {
* {@link #onDisplayEmpty(int)} will be called. If the value topActivity is cached, it
* should be cleared when {@link #onDisplayEmpty(int)} is called.
*
- * @param displayId The display ID on which the activity change happened.
+ * @param displayId The display ID on which the activity change happened.
* @param topActivity The component name of the top activity.
- * @param userId The user ID associated with the top activity.
+ * @param userId The user ID associated with the top activity.
*/
default void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity,
@UserIdInt int userId) {}
@@ -987,33 +795,6 @@ public final class VirtualDeviceManager {
}
/**
- * A wrapper for {@link ActivityListener} that executes callbacks on the given executor.
- */
- private static class ActivityListenerDelegate {
- @NonNull private final ActivityListener mActivityListener;
- @NonNull private final Executor mExecutor;
-
- ActivityListenerDelegate(@NonNull ActivityListener listener, @NonNull Executor executor) {
- mActivityListener = listener;
- mExecutor = executor;
- }
-
- public void onTopActivityChanged(int displayId, ComponentName topActivity) {
- mExecutor.execute(() -> mActivityListener.onTopActivityChanged(displayId, topActivity));
- }
-
- public void onTopActivityChanged(int displayId, ComponentName topActivity,
- @UserIdInt int userId) {
- mExecutor.execute(() ->
- mActivityListener.onTopActivityChanged(displayId, topActivity, userId));
- }
-
- public void onDisplayEmpty(int displayId) {
- mExecutor.execute(() -> mActivityListener.onDisplayEmpty(displayId));
- }
- }
-
- /**
* Interceptor interface to be called when an intent matches the IntentFilter passed into {@link
* VirtualDevice#registerIntentInterceptor}. When the interceptor is called after matching the
* IntentFilter, the intended activity launch will be aborted and alternatively replaced by
@@ -1035,33 +816,8 @@ public final class VirtualDeviceManager {
}
/**
- * A wrapper for {@link IntentInterceptorCallback} that executes callbacks on the
- * the given executor.
- */
- private static class VirtualIntentInterceptorDelegate
- extends IVirtualDeviceIntentInterceptor.Stub {
- @NonNull private final IntentInterceptorCallback mIntentInterceptorCallback;
- @NonNull private final Executor mExecutor;
-
- private VirtualIntentInterceptorDelegate(Executor executor,
- IntentInterceptorCallback interceptorCallback) {
- mExecutor = executor;
- mIntentInterceptorCallback = interceptorCallback;
- }
-
- @Override
- public void onIntentIntercepted(Intent intent) {
- final long token = Binder.clearCallingIdentity();
- try {
- mExecutor.execute(() -> mIntentInterceptorCallback.onIntentIntercepted(intent));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- }
-
- /**
* Listener for system sound effect playback on virtual device.
+ *
* @hide
*/
@SystemApi
@@ -1070,27 +826,9 @@ public final class VirtualDeviceManager {
/**
* Called when there's a system sound effect to be played on virtual device.
*
- * @param effectType - system sound effect type, see
- * {@code android.media.AudioManager.SystemSoundEffect}
+ * @param effectType - system sound effect type
+ * @see android.media.AudioManager.SystemSoundEffect
*/
void onPlaySoundEffect(@AudioManager.SystemSoundEffect int effectType);
}
-
- /**
- * A wrapper for {@link SoundEffectListener} that executes callbacks on the given executor.
- */
- private static class SoundEffectListenerDelegate {
- @NonNull private final SoundEffectListener mSoundEffectListener;
- @NonNull private final Executor mExecutor;
-
- private SoundEffectListenerDelegate(Executor executor,
- SoundEffectListener soundEffectCallback) {
- mSoundEffectListener = soundEffectCallback;
- mExecutor = executor;
- }
-
- public void onPlaySoundEffect(@AudioManager.SystemSoundEffect int effectType) {
- mExecutor.execute(() -> mSoundEffectListener.onPlaySoundEffect(effectType));
- }
- }
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 9a34dbe2699c..45d6dc62bfe8 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -34,6 +34,7 @@ import android.companion.virtual.sensor.VirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorDirectChannelCallback;
import android.content.ComponentName;
+import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SharedMemory;
@@ -680,7 +681,7 @@ public final class VirtualDeviceParams implements Parcelable {
* {@link #NAVIGATION_POLICY_DEFAULT_ALLOWED}, meaning activities are allowed to launch
* unless they are in {@code blockedCrossTaskNavigations}.
*
- * <p> This method must not be called if {@link #setAllowedCrossTaskNavigations(Set)} has
+ * <p>This method must not be called if {@link #setAllowedCrossTaskNavigations(Set)} has
* been called.
*
* @throws IllegalArgumentException if {@link #setAllowedCrossTaskNavigations(Set)} has
@@ -847,11 +848,11 @@ public final class VirtualDeviceParams implements Parcelable {
* <p>Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_AUDIO},
* otherwise {@link #build()} method will throw {@link IllegalArgumentException} if
* the playback session id is set to value other than
- * {@link android.media.AudioManager.AUDIO_SESSION_ID_GENERATE}.
+ * {@link android.media.AudioManager#AUDIO_SESSION_ID_GENERATE}.
*
* @param playbackSessionId requested device-specific audio session id for playback
- * @see android.media.AudioManager.generateAudioSessionId()
- * @see android.media.AudioTrack.Builder.setContext(Context)
+ * @see android.media.AudioManager#generateAudioSessionId()
+ * @see android.media.AudioTrack.Builder#setContext(Context)
*/
@NonNull
public Builder setAudioPlaybackSessionId(int playbackSessionId) {
@@ -871,11 +872,11 @@ public final class VirtualDeviceParams implements Parcelable {
* <p>Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_AUDIO},
* otherwise {@link #build()} method will throw {@link IllegalArgumentException} if
* the recording session id is set to value other than
- * {@link android.media.AudioManager.AUDIO_SESSION_ID_GENERATE}.
+ * {@link android.media.AudioManager#AUDIO_SESSION_ID_GENERATE}.
*
* @param recordingSessionId requested device-specific audio session id for playback
- * @see android.media.AudioManager.generateAudioSessionId()
- * @see android.media.AudioRecord.Builder.setContext(Context)
+ * @see android.media.AudioManager#generateAudioSessionId()
+ * @see android.media.AudioRecord.Builder#setContext(Context)
*/
@NonNull
public Builder setAudioRecordingSessionId(int recordingSessionId) {
diff --git a/core/java/android/companion/virtual/audio/AudioCapture.java b/core/java/android/companion/virtual/audio/AudioCapture.java
index d6d0d2b79c83..dd5e660b998e 100644
--- a/core/java/android/companion/virtual/audio/AudioCapture.java
+++ b/core/java/android/companion/virtual/audio/AudioCapture.java
@@ -56,12 +56,12 @@ public final class AudioCapture {
/**
* Sets the {@link AudioRecord} to handle audio capturing.
- * Callers may call this multiple times with different audio records to change
- * the underlying {@link AudioRecord} without stopping and re-starting recording.
*
- * @param audioRecord The underlying {@link AudioRecord} to use for capture,
- * or null if no audio (i.e. silence) should be captured while still keeping the
- * record in a recording state.
+ * <p>Callers may call this multiple times with different audio records to change the underlying
+ * {@link AudioRecord} without stopping and re-starting recording.
+ *
+ * @param audioRecord The underlying {@link AudioRecord} to use for capture, or null if no audio
+ * (i.e. silence) should be captured while still keeping the record in a recording state.
*/
void setAudioRecord(@Nullable AudioRecord audioRecord) {
Log.d(TAG, "set AudioRecord with " + audioRecord);
diff --git a/core/java/android/companion/virtual/audio/AudioInjection.java b/core/java/android/companion/virtual/audio/AudioInjection.java
index 9d6a3eb84351..5de5f7ef4779 100644
--- a/core/java/android/companion/virtual/audio/AudioInjection.java
+++ b/core/java/android/companion/virtual/audio/AudioInjection.java
@@ -65,12 +65,12 @@ public final class AudioInjection {
/**
* Sets the {@link AudioTrack} to handle audio injection.
- * Callers may call this multiple times with different audio tracks to change
- * the underlying {@link AudioTrack} without stopping and re-starting injection.
*
- * @param audioTrack The underlying {@link AudioTrack} to use for injection,
- * or null if no audio (i.e. silence) should be injected while still keeping the
- * record in a playing state.
+ * <p>Callers may call this multiple times with different audio tracks to change the underlying
+ * {@link AudioTrack} without stopping and re-starting injection.
+ *
+ * @param audioTrack The underlying {@link AudioTrack} to use for injection, or null if no audio
+ * (i.e. silence) should be injected while still keeping the record in a playing state.
*/
void setAudioTrack(@Nullable AudioTrack audioTrack) {
Log.d(TAG, "set AudioTrack with " + audioTrack);
diff --git a/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl b/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl
index 3cb0572f3350..dcdb6c6b5f7e 100644
--- a/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl
+++ b/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl
@@ -33,7 +33,7 @@ oneway interface IVirtualSensorCallback {
* @param enabled Whether the sensor is enabled.
* @param samplingPeriodMicros The requested sensor's sampling period in microseconds.
* @param batchReportingLatencyMicros The requested maximum time interval in microseconds
- * between the delivery of two batches of sensor events.
+ * between the delivery of two batches of sensor events.
*/
void onConfigurationChanged(in VirtualSensor sensor, boolean enabled, int samplingPeriodMicros,
int batchReportLatencyMicros);
@@ -60,7 +60,7 @@ oneway interface IVirtualSensorCallback {
* @param sensor The sensor, for which the channel was configured.
* @param rateLevel The rate level used to configure the direct sensor channel.
* @param reportToken A positive sensor report token, used to differentiate between events from
- * different sensors within the same channel.
+ * different sensors within the same channel.
*/
void onDirectChannelConfigured(int channelHandle, in VirtualSensor sensor, int rateLevel,
int reportToken);
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java
index bda44d402823..eaa17925b14b 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensor.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java
@@ -30,7 +30,7 @@ import android.os.RemoteException;
* Representation of a sensor on a remote device, capable of sending events, such as an
* accelerometer or a gyroscope.
*
- * This registers the sensor device with the sensor framework as a runtime sensor.
+ * <p>A virtual sensor device is registered with the sensor framework as a runtime sensor.
*
* @hide
*/
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java b/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java
index e6bd6daa060f..4d586f681b49 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java
@@ -45,10 +45,10 @@ public interface VirtualSensorCallback {
*
* @param sensor The sensor whose requested injection parameters have changed.
* @param enabled Whether the sensor is enabled. True if any listeners are currently registered,
- * and false otherwise.
+ * and false otherwise.
* @param samplingPeriod The requested sampling period of the sensor.
* @param batchReportLatency The requested maximum time interval between the delivery of two
- * batches of sensor events.
+ * batches of sensor events.
*/
void onConfigurationChanged(@NonNull VirtualSensor sensor, boolean enabled,
@NonNull Duration samplingPeriod, @NonNull Duration batchReportLatency);
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
index ef55ca97585d..3bdf9aa8015b 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
@@ -31,7 +31,9 @@ import java.util.Objects;
/**
* Configuration for creation of a virtual sensor.
+ *
* @see VirtualSensor
+ *
* @hide
*/
@SystemApi
@@ -122,6 +124,7 @@ public final class VirtualSensorConfig implements Parcelable {
/**
* Returns the vendor string of the sensor.
+ *
* @see Builder#setVendor
*/
@Nullable
@@ -130,7 +133,8 @@ public final class VirtualSensorConfig implements Parcelable {
}
/**
- * Returns maximum range of the sensor in the sensor's unit.
+ * Returns the maximum range of the sensor in the sensor's unit.
+ *
* @see Sensor#getMaximumRange
*/
public float getMaximumRange() {
@@ -138,7 +142,8 @@ public final class VirtualSensorConfig implements Parcelable {
}
/**
- * Returns The resolution of the sensor in the sensor's unit.
+ * Returns the resolution of the sensor in the sensor's unit.
+ *
* @see Sensor#getResolution
*/
public float getResolution() {
@@ -146,7 +151,8 @@ public final class VirtualSensorConfig implements Parcelable {
}
/**
- * Returns The power in mA used by this sensor while in use.
+ * Returns the power in mA used by this sensor while in use.
+ *
* @see Sensor#getPower
*/
public float getPower() {
@@ -154,8 +160,9 @@ public final class VirtualSensorConfig implements Parcelable {
}
/**
- * Returns The minimum delay allowed between two events in microseconds, or zero depending on
+ * Returns the minimum delay allowed between two events in microseconds, or zero depending on
* the sensor type.
+ *
* @see Sensor#getMinDelay
*/
public int getMinDelay() {
@@ -163,7 +170,8 @@ public final class VirtualSensorConfig implements Parcelable {
}
/**
- * Returns The maximum delay between two sensor events in microseconds.
+ * Returns the maximum delay between two sensor events in microseconds.
+ *
* @see Sensor#getMaxDelay
*/
public int getMaxDelay() {
@@ -201,6 +209,7 @@ public final class VirtualSensorConfig implements Parcelable {
/**
* Returns the sensor flags.
+ *
* @hide
*/
public int getFlags() {
@@ -233,7 +242,7 @@ public final class VirtualSensorConfig implements Parcelable {
*
* @param type The type of the sensor, matching {@link Sensor#getType}.
* @param name The name of the sensor. Must be unique among all sensors with the same type
- * that belong to the same virtual device.
+ * that belong to the same virtual device.
*/
public Builder(@IntRange(from = 1) int type, @NonNull String name) {
if (type <= 0) {
@@ -275,6 +284,7 @@ public final class VirtualSensorConfig implements Parcelable {
/**
* Sets the maximum range of the sensor in the sensor's unit.
+ *
* @see Sensor#getMaximumRange
*/
@NonNull
@@ -285,6 +295,7 @@ public final class VirtualSensorConfig implements Parcelable {
/**
* Sets the resolution of the sensor in the sensor's unit.
+ *
* @see Sensor#getResolution
*/
@NonNull
@@ -295,6 +306,7 @@ public final class VirtualSensorConfig implements Parcelable {
/**
* Sets the power in mA used by this sensor while in use.
+ *
* @see Sensor#getPower
*/
@NonNull
@@ -305,6 +317,7 @@ public final class VirtualSensorConfig implements Parcelable {
/**
* Sets the minimum delay allowed between two events in microseconds.
+ *
* @see Sensor#getMinDelay
*/
@NonNull
@@ -315,6 +328,7 @@ public final class VirtualSensorConfig implements Parcelable {
/**
* Sets the maximum delay between two sensor events in microseconds.
+ *
* @see Sensor#getMaxDelay
*/
@NonNull
@@ -339,11 +353,11 @@ public final class VirtualSensorConfig implements Parcelable {
* Sets whether direct sensor channel of the given types is supported.
*
* @param memoryTypes A combination of {@link SensorDirectChannel.MemoryType} flags
- * indicating the types of shared memory supported for creating direct channels. Only
- * {@link SensorDirectChannel#TYPE_MEMORY_FILE} direct channels may be supported for virtual
- * sensors.
+ * indicating the types of shared memory supported for creating direct channels. Only
+ * {@link SensorDirectChannel#TYPE_MEMORY_FILE} direct channels may be supported for
+ * virtual sensors.
* @throws IllegalArgumentException if {@link SensorDirectChannel#TYPE_HARDWARE_BUFFER} is
- * set to be supported.
+ * set to be supported.
*/
@NonNull
public VirtualSensorConfig.Builder setDirectChannelTypesSupported(
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelCallback.java b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelCallback.java
index d352f94ffd76..f10e9d087a47 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelCallback.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelCallback.java
@@ -45,6 +45,8 @@ import android.os.SharedMemory;
* <p>The callback is tied to the VirtualDevice's lifetime as the virtual sensors are created when
* the device is created and destroyed when the device is destroyed.
*
+ * @see VirtualSensorDirectChannelWriter
+ *
* @hide
*/
@SystemApi
@@ -94,7 +96,7 @@ public interface VirtualSensorDirectChannelCallback {
* @param sensor The sensor, for which the channel was configured.
* @param rateLevel The rate level used to configure the direct sensor channel.
* @param reportToken A positive sensor report token, used to differentiate between events from
- * different sensors within the same channel.
+ * different sensors within the same channel.
*
* @see VirtualSensorConfig.Builder#setHighestDirectReportRateLevel(int)
* @see VirtualSensorConfig.Builder#setDirectChannelTypesSupported(int)
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
index 6aed96ff593e..bf78dd09e7c2 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
@@ -41,6 +41,41 @@ import java.util.concurrent.atomic.AtomicLong;
* write the events from the relevant sensors directly to the shared memory regions of the
* corresponding {@link SensorDirectChannel} instances.
*
+ * <p>Example:
+ * <p>During sensor and virtual device creation:
+ * <pre>
+ * VirtualSensorDirectChannelWriter writer = new VirtualSensorDirectChannelWriter();
+ * VirtualSensorDirectChannelCallback callback = new VirtualSensorDirectChannelCallback() {
+ * @Override
+ * public void onDirectChannelCreated(int channelHandle, SharedMemory sharedMemory) {
+ * writer.addChannel(channelHandle, sharedMemory);
+ * }
+ * @Override
+ * public void onDirectChannelDestroyed(int channelHandle);
+ * writer.removeChannel(channelHandle);
+ * }
+ * @Override
+ * public void onDirectChannelConfigured(int channelHandle, VirtualSensor sensor, int rateLevel,
+ * int reportToken)
+ * if (!writer.configureChannel(channelHandle, sensor, rateLevel, reportToken)) {
+ * // handle error
+ * }
+ * }
+ * }
+ * </pre>
+ * <p>During the virtual device lifetime:
+ * <pre>
+ * VirtualSensor sensor = ...
+ * while (shouldInjectEvents(sensor)) {
+ * if (!writer.writeSensorEvent(sensor, event)) {
+ * // handle error
+ * }
+ * }
+ * writer.close();
+ * </pre>
+ * <p>Note that the virtual device owner should take the currently configured rate level into
+ * account when deciding whether and how often to inject events for a particular sensor.
+ *
* @see android.hardware.SensorDirectChannel#configure
* @see VirtualSensorDirectChannelCallback
*
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
index 01b49750572d..a368467ee8f2 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
@@ -121,7 +121,7 @@ public final class VirtualSensorEvent implements Parcelable {
* monotonically increasing using the same time base as
* {@link android.os.SystemClock#elapsedRealtimeNanos()}.
*
- * If not explicitly set, the current timestamp is used for the sensor event.
+ * <p>If not explicitly set, the current timestamp is used for the sensor event.
*
* @see android.hardware.SensorEvent#timestamp
*/
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 0e4c3c0f12a1..e908ced06acd 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -41,9 +41,12 @@ import java.util.Set;
* <p>The properties describing a
* {@link CameraDevice CameraDevice}.</p>
*
- * <p>These properties are fixed for a given CameraDevice, and can be queried
+ * <p>These properties are primarily fixed for a given CameraDevice, and can be queried
* through the {@link CameraManager CameraManager}
- * interface with {@link CameraManager#getCameraCharacteristics}.</p>
+ * interface with {@link CameraManager#getCameraCharacteristics}. Beginning with API level 32, some
+ * properties such as {@link #SENSOR_ORIENTATION} may change dynamically based on the state of the
+ * device. For information on whether a specific value is fixed, see the documentation for its key.
+ * </p>
*
* <p>When obtained by a client that does not hold the CAMERA permission, some metadata values are
* not included. The list of keys that require the permission is given by
@@ -281,9 +284,6 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <p>The field definitions can be
* found in {@link CameraCharacteristics}.</p>
*
- * <p>Querying the value for the same key more than once will return a value
- * which is equal to the previous queried value.</p>
- *
* @throws IllegalArgumentException if the key was not valid
*
* @param key The characteristics field to read.
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index a7e28e2f40d1..4950373449e2 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -41,9 +41,10 @@ import java.util.List;
* </p>
*
* <p>
- * All instances of CameraMetadata are immutable. The list of keys with {@link #getKeys()}
- * never changes, nor do the values returned by any key with {@code #get} throughout
- * the lifetime of the object.
+ * All instances of CameraMetadata are immutable. Beginning with API level 32, the list of keys
+ * returned by {@link #getKeys()} may change depending on the state of the device, as may the
+ * values returned by any key with {@code #get} throughout the lifetime of the object. For
+ * information on whether a specific value is fixed, see the documentation for its key.
* </p>
*
* @see CameraDevice
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index e7385b62faa6..9cacfff4b33a 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -39,6 +39,7 @@ import android.os.InputEventInjectionSync;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Vibrator;
+import android.sysprop.InputProperties;
import android.util.Log;
import android.view.Display;
import android.view.InputDevice;
@@ -1123,7 +1124,8 @@ public final class InputManager {
public boolean isStylusPointerIconEnabled() {
if (mIsStylusPointerIconEnabled == null) {
mIsStylusPointerIconEnabled = getContext().getResources()
- .getBoolean(com.android.internal.R.bool.config_enableStylusPointerIcon);
+ .getBoolean(com.android.internal.R.bool.config_enableStylusPointerIcon)
+ || InputProperties.force_enable_stylus_pointer_icon().orElse(false);
}
return mIsStylusPointerIconEnabled;
}
diff --git a/core/java/android/hardware/soundtrigger/OWNERS b/core/java/android/hardware/soundtrigger/OWNERS
index 01b2cb981bbb..1e41886fe716 100644
--- a/core/java/android/hardware/soundtrigger/OWNERS
+++ b/core/java/android/hardware/soundtrigger/OWNERS
@@ -1,2 +1 @@
-atneya@google.com
-elaurent@google.com
+include /media/java/android/media/soundtrigger/OWNERS
diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
index 38b3174abd4c..46cf0163c0e5 100644
--- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
@@ -354,6 +354,7 @@ public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetwork
}
/** @hide */
+ @Override
public Map<Integer, Integer> getCapabilitiesMatchCriteria() {
return Collections.unmodifiableMap(new HashMap<>(mCapabilitiesMatchCriteria));
}
diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java
index 6f9c9dd918d1..a27e9230d473 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/core/java/android/net/vcn/VcnConfig.java
@@ -16,6 +16,7 @@
package android.net.vcn;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static com.android.internal.annotations.VisibleForTesting.Visibility;
@@ -75,6 +76,7 @@ public final class VcnConfig implements Parcelable {
static {
ALLOWED_TRANSPORTS.add(TRANSPORT_WIFI);
ALLOWED_TRANSPORTS.add(TRANSPORT_CELLULAR);
+ ALLOWED_TRANSPORTS.add(TRANSPORT_TEST);
}
private static final String PACKAGE_NAME_KEY = "mPackageName";
@@ -155,6 +157,11 @@ public final class VcnConfig implements Parcelable {
+ transport
+ " which might be from a new version of VcnConfig");
}
+
+ if (transport == TRANSPORT_TEST && !mIsTestModeProfile) {
+ throw new IllegalArgumentException(
+ "Found TRANSPORT_TEST in a non-test-mode profile");
+ }
}
}
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
index 9235d0913295..edf2c093bc8b 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
@@ -29,6 +29,7 @@ import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
import java.util.Objects;
/**
@@ -307,4 +308,7 @@ public abstract class VcnUnderlyingNetworkTemplate {
public int getMinExitDownstreamBandwidthKbps() {
return mMinExitDownstreamBandwidthKbps;
}
+
+ /** @hide */
+ public abstract Map<Integer, Integer> getCapabilitiesMatchCriteria();
}
diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
index 2544a6d63561..2e6b09f032fb 100644
--- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
@@ -15,6 +15,9 @@
*/
package android.net.vcn;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
+
import static com.android.internal.annotations.VisibleForTesting.Visibility;
import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
import static com.android.server.vcn.util.PersistableBundleUtils.STRING_SERIALIZER;
@@ -23,6 +26,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.net.NetworkCapabilities;
+import android.net.vcn.VcnUnderlyingNetworkTemplate.MatchCriteria;
import android.os.PersistableBundle;
import android.util.ArraySet;
@@ -32,6 +36,7 @@ import com.android.server.vcn.util.PersistableBundleUtils;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -162,6 +167,12 @@ public final class VcnWifiUnderlyingNetworkTemplate extends VcnUnderlyingNetwork
return Collections.unmodifiableSet(mSsids);
}
+ /** @hide */
+ @Override
+ public Map<Integer, Integer> getCapabilitiesMatchCriteria() {
+ return Collections.singletonMap(NET_CAPABILITY_INTERNET, MATCH_REQUIRED);
+ }
+
/** This class is used to incrementally build VcnWifiUnderlyingNetworkTemplate objects. */
public static final class Builder {
private int mMeteredMatchCriteria = MATCH_ANY;
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index bfc5afe61265..4bfff16d973f 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -8434,7 +8434,7 @@ public final class ContactsContract {
extras.putString(KEY_ACCOUNT_NAME, accountName);
extras.putString(KEY_ACCOUNT_TYPE, accountType);
- contentResolver.call(ContactsContract.AUTHORITY_URI,
+ nullSafeCall(contentResolver, ContactsContract.AUTHORITY_URI,
ContactsContract.SimContacts.ADD_SIM_ACCOUNT_METHOD,
null, extras);
}
@@ -8457,7 +8457,7 @@ public final class ContactsContract {
Bundle extras = new Bundle();
extras.putInt(KEY_SIM_SLOT_INDEX, simSlotIndex);
- contentResolver.call(ContactsContract.AUTHORITY_URI,
+ nullSafeCall(contentResolver, ContactsContract.AUTHORITY_URI,
ContactsContract.SimContacts.REMOVE_SIM_ACCOUNT_METHOD,
null, extras);
}
@@ -8469,7 +8469,7 @@ public final class ContactsContract {
*/
public static @NonNull List<SimAccount> getSimAccounts(
@NonNull ContentResolver contentResolver) {
- Bundle response = contentResolver.call(ContactsContract.AUTHORITY_URI,
+ Bundle response = nullSafeCall(contentResolver, ContactsContract.AUTHORITY_URI,
ContactsContract.SimContacts.QUERY_SIM_ACCOUNTS_METHOD,
null, null);
List<SimAccount> result = response.getParcelableArrayList(KEY_SIM_ACCOUNTS, android.provider.ContactsContract.SimAccount.class);
@@ -9088,7 +9088,8 @@ public final class ContactsContract {
* @param contactId the id of the contact to undemote.
*/
public static void undemote(ContentResolver contentResolver, long contactId) {
- contentResolver.call(ContactsContract.AUTHORITY_URI, PinnedPositions.UNDEMOTE_METHOD,
+ nullSafeCall(contentResolver, ContactsContract.AUTHORITY_URI,
+ PinnedPositions.UNDEMOTE_METHOD,
String.valueOf(contactId), null);
}
@@ -10300,4 +10301,13 @@ public final class ContactsContract {
public static final String CONTENT_ITEM_TYPE =
"vnd.android.cursor.item/contact_metadata_sync_state";
}
+
+ private static Bundle nullSafeCall(@NonNull ContentResolver resolver, @NonNull Uri uri,
+ @NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
+ try (ContentProviderClient client = resolver.acquireContentProviderClient(uri)) {
+ return client.call(method, arg, extras);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
}
diff --git a/core/java/android/service/autofill/FillContext.java b/core/java/android/service/autofill/FillContext.java
index cc1b6cda82bb..e36d899e02ee 100644
--- a/core/java/android/service/autofill/FillContext.java
+++ b/core/java/android/service/autofill/FillContext.java
@@ -127,7 +127,7 @@ public final class FillContext implements Parcelable {
final int index = missingNodeIndexes.keyAt(i);
final AutofillId id = ids[index];
- if (id.equals(node.getAutofillId())) {
+ if (id != null && id.equals(node.getAutofillId())) {
foundNodes[index] = node;
if (mViewNodeLookupTable == null) {
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index 6a493e6c6ff1..d88994b7296e 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -57,8 +57,9 @@ public final class PointerIcon implements Parcelable {
/** Type constant: Null icon. It has no bitmap. */
public static final int TYPE_NULL = 0;
- /** Type constant: no icons are specified. If all views uses this, then falls back
- * to the default type, but this is helpful to distinguish a view explicitly want
+ /**
+ * Type constant: no icons are specified. If all views uses this, then the pointer icon falls
+ * back to the default type, but this is helpful to distinguish a view that explicitly wants
* to have the default icon.
* @hide
*/
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 6cd894113ca6..003307db832a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -92,6 +92,7 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.input.InputManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -5413,7 +5414,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* The pointer icon when the mouse hovers on this view. The default is null.
*/
- private PointerIcon mPointerIcon;
+ private PointerIcon mMousePointerIcon;
/**
* @hide
@@ -29506,30 +29507,71 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Returns the pointer icon for the motion event, or null if it doesn't specify the icon.
- * The default implementation does not care the location or event types, but some subclasses
- * may use it (such as WebViews).
- * @param event The MotionEvent from a mouse
- * @param pointerIndex The index of the pointer for which to retrieve the {@link PointerIcon}.
- * This will be between 0 and {@link MotionEvent#getPointerCount()}.
+ * Resolve the pointer icon that should be used for specified pointer in the motion event.
+ *
+ * The default implementation will resolve the pointer icon to one set using
+ * {@link #setPointerIcon(PointerIcon)} for mouse devices. Subclasses may override this to
+ * customize the icon for the given pointer.
+ *
+ * For example, the pointer icon for a stylus pointer can be resolved in the following way:
+ * <code><pre>
+ * &#64;Override
+ * public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
+ * final int toolType = event.getToolType(pointerIndex);
+ * if (!event.isFromSource(InputDevice.SOURCE_MOUSE)
+ * && event.isFromSource(InputDevice.SOURCE_STYLUS)
+ * && (toolType == MotionEvent.TOOL_TYPE_STYLUS
+ * || toolType == MotionEvent.TOOL_TYPE_ERASER)) {
+ * // Show this pointer icon only if this pointer is a stylus.
+ * return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_WAIT);
+ * }
+ * // Use the default logic for determining the pointer icon for other non-stylus pointers,
+ * // like for the mouse cursor.
+ * return super.onResolvePointerIcon(event, pointerIndex);
+ * }
+ * </pre></code>
+ *
+ * @param event The {@link MotionEvent} that requires a pointer icon to be resolved for one of
+ * pointers.
+ * @param pointerIndex The index of the pointer in {@code event} for which to retrieve the
+ * {@link PointerIcon}. This will be between 0 and {@link MotionEvent#getPointerCount()}.
+ * @return the pointer icon to use for specified pointer, or {@code null} if a pointer icon
+ * is not specified and the default icon should be used.
* @see PointerIcon
+ * @see InputManager#isStylusPointerIconEnabled()
*/
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
if (isDraggingScrollBar() || isOnScrollbarThumb(x, y)) {
- return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_ARROW);
+ // Use the default pointer icon.
+ return null;
+ }
+
+ // Note: A drawing tablet will have both SOURCE_MOUSE and SOURCE_STYLUS, but it would use
+ // TOOL_TYPE_STYLUS. For now, treat drawing tablets the same way as a mouse or touchpad.
+ if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+ return mMousePointerIcon;
}
- return mPointerIcon;
+
+ return null;
}
/**
- * Set the pointer icon for the current view.
+ * Set the pointer icon to be used for a mouse pointer in the current view.
+ *
* Passing {@code null} will restore the pointer icon to its default value.
+ * Note that setting the pointer icon using this method will only set it for events coming from
+ * a mouse device (i.e. with source {@link InputDevice#SOURCE_MOUSE}). To resolve
+ * the pointer icon for other device types like styluses, override
+ * {@link #onResolvePointerIcon(MotionEvent, int)}.
+ *
* @param pointerIcon A PointerIcon instance which will be shown when the mouse hovers.
+ * @see #onResolvePointerIcon(MotionEvent, int)
+ * @see PointerIcon
*/
public void setPointerIcon(PointerIcon pointerIcon) {
- mPointerIcon = pointerIcon;
+ mMousePointerIcon = pointerIcon;
if (mAttachInfo == null || mAttachInfo.mHandlingPointerEvent) {
return;
}
@@ -29540,11 +29582,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Gets the pointer icon for the current view.
+ * Gets the mouse pointer icon for the current view.
+ *
+ * @see #setPointerIcon(PointerIcon)
*/
@InspectableProperty
public PointerIcon getPointerIcon() {
- return mPointerIcon;
+ return mMousePointerIcon;
}
/**
diff --git a/core/java/android/view/autofill/AutofillClientController.java b/core/java/android/view/autofill/AutofillClientController.java
index 93d98ac51c3b..3a8e8027b88e 100644
--- a/core/java/android/view/autofill/AutofillClientController.java
+++ b/core/java/android/view/autofill/AutofillClientController.java
@@ -350,6 +350,10 @@ public final class AutofillClientController implements AutofillManager.AutofillC
final boolean[] visible = new boolean[autofillIdCount];
for (int i = 0; i < autofillIdCount; i++) {
final AutofillId autofillId = autofillIds[i];
+ if (autofillId == null) {
+ visible[i] = false;
+ continue;
+ }
final View view = autofillClientFindViewByAutofillIdTraversal(autofillId);
if (view != null) {
if (!autofillId.isVirtualInt()) {
@@ -383,6 +387,7 @@ public final class AutofillClientController implements AutofillManager.AutofillC
@Override
public View autofillClientFindViewByAutofillIdTraversal(AutofillId autofillId) {
+ if (autofillId == null) return null;
final ArrayList<ViewRootImpl> roots =
WindowManagerGlobal.getInstance().getRootViews(mActivity.getActivityToken());
for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
@@ -410,7 +415,7 @@ public final class AutofillClientController implements AutofillManager.AutofillC
if (rootView != null) {
final int viewCount = autofillIds.length;
for (int viewNum = 0; viewNum < viewCount; viewNum++) {
- if (views[viewNum] == null) {
+ if (autofillIds[viewNum] != null && views[viewNum] == null) {
views[viewNum] = rootView.findViewByAutofillIdTraversal(
autofillIds[viewNum].getViewId());
}
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index eb91d08dc278..ec50c697ae9a 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -405,21 +405,15 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
}
if (handler.getLooper().isCurrentThread()) {
servedView.onInputConnectionClosedInternal();
- final ViewRootImpl viewRoot = servedView.getViewRootImpl();
- if (viewRoot != null) {
- viewRoot.getHandwritingInitiator().onInputConnectionClosed(servedView);
- }
} else {
handler.post(servedView::onInputConnectionClosedInternal);
- handler.post(() -> {
- final ViewRootImpl viewRoot = servedView.getViewRootImpl();
- if (viewRoot != null) {
- viewRoot.getHandwritingInitiator()
- .onInputConnectionClosed(servedView);
- }
- });
}
}
+
+ final ViewRootImpl viewRoot = servedView.getViewRootImpl();
+ if (viewRoot != null) {
+ viewRoot.getHandwritingInitiator().onInputConnectionClosed(servedView);
+ }
}
});
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index d56a06fbd127..67c9f8ca0048 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6232,7 +6232,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_lineHeight
*/
@android.view.RemotableViewMethod
- public void setLineHeight(int unit, @FloatRange(from = 0) float lineHeight) {
+ public void setLineHeight(
+ @TypedValue.ComplexDimensionUnit int unit,
+ @FloatRange(from = 0) float lineHeight
+ ) {
setLineHeightPx(
TypedValue.applyDimension(unit, lineHeight, getDisplayMetricsOrSystem()));
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 85cb15bdd906..a95ce64ecec8 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -51,6 +51,7 @@ import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
import android.util.TimingsTraceLog;
+import android.view.WindowManager;
import android.webkit.WebViewFactory;
import android.widget.TextView;
@@ -72,6 +73,8 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.Provider;
import java.security.Security;
+import java.util.ArrayList;
+import java.util.List;
/**
* Startup class for the zygote process.
@@ -384,33 +387,49 @@ public class ZygoteInit {
* classpath.
*/
private static void cacheNonBootClasspathClassLoaders() {
+ // Ordered dependencies first
+ final List<SharedLibraryInfo> libs = new ArrayList<>();
// These libraries used to be part of the bootclasspath, but had to be removed.
// Old system applications still get them for backwards compatibility reasons,
// so they are cached here in order to preserve performance characteristics.
- SharedLibraryInfo hidlBase = new SharedLibraryInfo(
+ libs.add(new SharedLibraryInfo(
"/system/framework/android.hidl.base-V1.0-java.jar", null /*packageName*/,
null /*codePaths*/, null /*name*/, 0 /*version*/, SharedLibraryInfo.TYPE_BUILTIN,
null /*declaringPackage*/, null /*dependentPackages*/, null /*dependencies*/,
- false /*isNative*/);
- SharedLibraryInfo hidlManager = new SharedLibraryInfo(
+ false /*isNative*/));
+ libs.add(new SharedLibraryInfo(
"/system/framework/android.hidl.manager-V1.0-java.jar", null /*packageName*/,
null /*codePaths*/, null /*name*/, 0 /*version*/, SharedLibraryInfo.TYPE_BUILTIN,
null /*declaringPackage*/, null /*dependentPackages*/, null /*dependencies*/,
- false /*isNative*/);
+ false /*isNative*/));
- SharedLibraryInfo androidTestBase = new SharedLibraryInfo(
+ libs.add(new SharedLibraryInfo(
"/system/framework/android.test.base.jar", null /*packageName*/,
null /*codePaths*/, null /*name*/, 0 /*version*/, SharedLibraryInfo.TYPE_BUILTIN,
null /*declaringPackage*/, null /*dependentPackages*/, null /*dependencies*/,
- false /*isNative*/);
-
- ApplicationLoaders.getDefault().createAndCacheNonBootclasspathSystemClassLoaders(
- new SharedLibraryInfo[]{
- // ordered dependencies first
- hidlBase,
- hidlManager,
- androidTestBase,
- });
+ false /*isNative*/));
+
+ // WindowManager Extensions is an optional shared library that is required for WindowManager
+ // Jetpack to fully function. Since it is a widely used library, preload it to improve apps
+ // startup performance.
+ if (WindowManager.hasWindowExtensionsEnabled()) {
+ final String systemExtFrameworkPath =
+ new File(Environment.getSystemExtDirectory(), "framework").getPath();
+ libs.add(new SharedLibraryInfo(
+ systemExtFrameworkPath + "/androidx.window.extensions.jar",
+ "androidx.window.extensions", null /*codePaths*/,
+ "androidx.window.extensions", SharedLibraryInfo.VERSION_UNDEFINED,
+ SharedLibraryInfo.TYPE_BUILTIN, null /*declaringPackage*/,
+ null /*dependentPackages*/, null /*dependencies*/, false /*isNative*/));
+ libs.add(new SharedLibraryInfo(
+ systemExtFrameworkPath + "/androidx.window.sidecar.jar",
+ "androidx.window.sidecar", null /*codePaths*/,
+ "androidx.window.sidecar", SharedLibraryInfo.VERSION_UNDEFINED,
+ SharedLibraryInfo.TYPE_BUILTIN, null /*declaringPackage*/,
+ null /*dependentPackages*/, null /*dependencies*/, false /*isNative*/));
+ }
+
+ ApplicationLoaders.getDefault().createAndCacheNonBootclasspathSystemClassLoaders(libs);
}
/**
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index a7c7d0ba35bc..b24dc8a9e63b 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -413,9 +413,13 @@ protected:
if (env->ExceptionCheck()) {
ScopedLocalRef<jthrowable> excep(env, env->ExceptionOccurred());
- binder_report_exception(env, excep.get(),
- "*** Uncaught remote exception! "
- "(Exceptions are not yet supported across processes.)");
+
+ auto state = IPCThreadState::self();
+ String8 msg;
+ msg.appendFormat("*** Uncaught remote exception! Exceptions are not yet supported "
+ "across processes. Client PID %d UID %d.",
+ state->getCallingPid(), state->getCallingUid());
+ binder_report_exception(env, excep.get(), msg.c_str());
res = JNI_FALSE;
}
@@ -431,6 +435,7 @@ protected:
ScopedLocalRef<jthrowable> excep(env, env->ExceptionOccurred());
binder_report_exception(env, excep.get(),
"*** Uncaught exception in onBinderStrictModePolicyChange");
+ // TODO: should turn this to fatal?
}
// Need to always call through the native implementation of
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1a85f4cad9e0..ef19fc1f5360 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6362,23 +6362,6 @@
<!-- Package name of Health Connect data migrator application. -->
<string name="config_healthConnectMigratorPackageName"></string>
- <!-- The Universal Stylus Initiative (USI) protocol version supported by each display.
- (@see https://universalstylus.org/).
-
- The i-th value in this array corresponds to the supported USI version of the i-th display
- listed in config_displayUniqueIdArray. On a single-display device, the
- config_displayUniqueIdArray may be empty, in which case the only value in this array should
- be the USI version for the main built-in display.
-
- If the display does not support USI, the version value should be an empty string. If the
- display supports USI, the version must be in the following format:
- "<major-version>.<minor-version>"
-
- For example, "", "1.0", and "2.0" are valid values. -->
- <string-array name="config_displayUsiVersionArray" translatable="false">
- <item>""</item>
- </string-array>
-
<!-- Whether system apps should be scanned in the stopped state during initial boot.
Packages can be added by OEMs in an allowlist, to prevent them from being scanned as
"stopped" during initial boot of a device, or after an OTA update. Stopped state of
@@ -6396,4 +6379,6 @@
<!-- Whether we should persist the brightness value in nits for the default display even if
the underlying display device changes. -->
<bool name="config_persistBrightnessNitsForDefaultDisplay">false</bool>
+ <!-- Whether to request the approval before commit sessions. -->
+ <bool name="config_isPreApprovalRequestAvailable">true</bool>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 79f3dcd8c1ed..8855d5b3de25 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2238,6 +2238,7 @@
<java-symbol type="array" name="config_nonPreemptibleInputMethods" />
<java-symbol type="bool" name="config_enhancedConfirmationModeEnabled" />
<java-symbol type="bool" name="config_persistBrightnessNitsForDefaultDisplay" />
+ <java-symbol type="bool" name="config_isPreApprovalRequestAvailable" />
<java-symbol type="layout" name="resolver_list" />
<java-symbol type="id" name="resolver_list" />
diff --git a/core/tests/coretests/src/android/app/ApplicationLoadersTest.java b/core/tests/coretests/src/android/app/ApplicationLoadersTest.java
index 19e7f80dfa5b..3cb62b97647b 100644
--- a/core/tests/coretests/src/android/app/ApplicationLoadersTest.java
+++ b/core/tests/coretests/src/android/app/ApplicationLoadersTest.java
@@ -16,14 +16,17 @@
package android.app;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import android.content.pm.SharedLibraryInfo;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.google.android.collect.Lists;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -48,7 +51,7 @@ public class ApplicationLoadersTest {
@Test
public void testGetNonExistantLib() {
ApplicationLoaders loaders = new ApplicationLoaders();
- assertEquals(null, loaders.getCachedNonBootclasspathSystemLib(
+ assertNull(loaders.getCachedNonBootclasspathSystemLib(
"/system/framework/nonexistantlib.jar", null, null, null));
}
@@ -57,9 +60,9 @@ public class ApplicationLoadersTest {
ApplicationLoaders loaders = new ApplicationLoaders();
SharedLibraryInfo libA = createLib(LIB_A);
- loaders.createAndCacheNonBootclasspathSystemClassLoaders(new SharedLibraryInfo[]{libA});
+ loaders.createAndCacheNonBootclasspathSystemClassLoaders(Lists.newArrayList(libA));
- assertNotEquals(null, loaders.getCachedNonBootclasspathSystemLib(
+ assertNotNull(loaders.getCachedNonBootclasspathSystemLib(
LIB_A, null, null, null));
}
@@ -71,9 +74,9 @@ public class ApplicationLoadersTest {
ClassLoader parent = ClassLoader.getSystemClassLoader();
assertNotEquals(null, parent);
- loaders.createAndCacheNonBootclasspathSystemClassLoaders(new SharedLibraryInfo[]{libA});
+ loaders.createAndCacheNonBootclasspathSystemClassLoaders(Lists.newArrayList(libA));
- assertEquals(null, loaders.getCachedNonBootclasspathSystemLib(
+ assertNull(loaders.getCachedNonBootclasspathSystemLib(
LIB_A, parent, null, null));
}
@@ -82,9 +85,9 @@ public class ApplicationLoadersTest {
ApplicationLoaders loaders = new ApplicationLoaders();
SharedLibraryInfo libA = createLib(LIB_A);
- loaders.createAndCacheNonBootclasspathSystemClassLoaders(new SharedLibraryInfo[]{libA});
+ loaders.createAndCacheNonBootclasspathSystemClassLoaders(Lists.newArrayList(libA));
- assertEquals(null, loaders.getCachedNonBootclasspathSystemLib(
+ assertNull(loaders.getCachedNonBootclasspathSystemLib(
LIB_A, null, "other classloader", null));
}
@@ -98,9 +101,9 @@ public class ApplicationLoadersTest {
ArrayList<ClassLoader> sharedLibraries = new ArrayList<>();
sharedLibraries.add(dep);
- loaders.createAndCacheNonBootclasspathSystemClassLoaders(new SharedLibraryInfo[]{libA});
+ loaders.createAndCacheNonBootclasspathSystemClassLoaders(Lists.newArrayList(libA));
- assertEquals(null, loaders.getCachedNonBootclasspathSystemLib(
+ assertNull(loaders.getCachedNonBootclasspathSystemLib(
LIB_A, null, null, sharedLibraries));
}
@@ -112,7 +115,7 @@ public class ApplicationLoadersTest {
libB.addDependency(libA);
loaders.createAndCacheNonBootclasspathSystemClassLoaders(
- new SharedLibraryInfo[]{libA, libB});
+ Lists.newArrayList(libA, libB));
ClassLoader loadA = loaders.getCachedNonBootclasspathSystemLib(
LIB_A, null, null, null);
@@ -121,7 +124,7 @@ public class ApplicationLoadersTest {
ArrayList<ClassLoader> sharedLibraries = new ArrayList<>();
sharedLibraries.add(loadA);
- assertNotEquals(null, loaders.getCachedNonBootclasspathSystemLib(
+ assertNotNull(loaders.getCachedNonBootclasspathSystemLib(
LIB_DEP_A, null, null, sharedLibraries));
}
@@ -132,7 +135,6 @@ public class ApplicationLoadersTest {
SharedLibraryInfo libB = createLib(LIB_DEP_A);
libB.addDependency(libA);
- loaders.createAndCacheNonBootclasspathSystemClassLoaders(
- new SharedLibraryInfo[]{libB, libA});
+ loaders.createAndCacheNonBootclasspathSystemClassLoaders(Lists.newArrayList(libB, libA));
}
}
diff --git a/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java b/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java
index 306ecdee9167..61e976bee35e 100644
--- a/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java
+++ b/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java
@@ -20,8 +20,6 @@ import static com.android.internal.util.LatencyTracker.ActionProperties.ENABLE_S
import static com.android.internal.util.LatencyTracker.ActionProperties.SAMPLE_INTERVAL_SUFFIX;
import static com.android.internal.util.LatencyTracker.ActionProperties.TRACE_THRESHOLD_SUFFIX;
-import static com.google.common.truth.Truth.assertThat;
-
import android.os.ConditionVariable;
import android.provider.DeviceConfig;
import android.util.Log;
@@ -33,7 +31,6 @@ import com.android.internal.annotations.GuardedBy;
import com.google.common.collect.ImmutableMap;
-import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -46,7 +43,6 @@ import java.util.concurrent.atomic.AtomicReference;
public final class FakeLatencyTracker extends LatencyTracker {
private static final String TAG = "FakeLatencyTracker";
- private static final Duration FORCE_UPDATE_TIMEOUT = Duration.ofSeconds(1);
private final Object mLock = new Object();
@GuardedBy("mLock")
@@ -203,7 +199,7 @@ public final class FakeLatencyTracker extends LatencyTracker {
}
}
Log.i(TAG, "waiting for condition");
- assertThat(mDeviceConfigPropertiesUpdated.block(FORCE_UPDATE_TIMEOUT.toMillis())).isTrue();
+ mDeviceConfigPropertiesUpdated.block();
}
public void waitForMatchingActionProperties(ActionProperties actionProperties)
@@ -232,7 +228,7 @@ public final class FakeLatencyTracker extends LatencyTracker {
}
}
Log.i(TAG, "waiting for condition");
- assertThat(mDeviceConfigPropertiesUpdated.block(FORCE_UPDATE_TIMEOUT.toMillis())).isTrue();
+ mDeviceConfigPropertiesUpdated.block();
}
public void waitForActionEnabledState(int action, boolean enabledState) throws Exception {
@@ -260,7 +256,7 @@ public final class FakeLatencyTracker extends LatencyTracker {
}
}
Log.i(TAG, "waiting for condition");
- assertThat(mDeviceConfigPropertiesUpdated.block(FORCE_UPDATE_TIMEOUT.toMillis())).isTrue();
+ mDeviceConfigPropertiesUpdated.block();
}
public void waitForGlobalEnabledState(boolean enabledState) throws Exception {
@@ -280,6 +276,6 @@ public final class FakeLatencyTracker extends LatencyTracker {
}
}
Log.i(TAG, "waiting for condition");
- assertThat(mDeviceConfigPropertiesUpdated.block(FORCE_UPDATE_TIMEOUT.toMillis())).isTrue();
+ mDeviceConfigPropertiesUpdated.block();
}
}
diff --git a/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml b/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml
deleted file mode 100644
index 8779cc09715b..000000000000
--- a/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="32.0dp"
- android:viewportWidth="32.0"
- android:viewportHeight="32.0"
->
- <group android:scaleX="0.5"
- android:scaleY="0.5"
- android:translateX="6.0"
- android:translateY="6.0">
- <path
- android:fillColor="@android:color/black"
- android:pathData="M5.958,37.708Q4.458,37.708 3.354,36.604Q2.25,35.5 2.25,34V18.292Q2.25,16.792 3.354,15.688Q4.458,14.583 5.958,14.583H9.5V5.958Q9.5,4.458 10.625,3.354Q11.75,2.25 13.208,2.25H34Q35.542,2.25 36.646,3.354Q37.75,4.458 37.75,5.958V21.667Q37.75,23.167 36.646,24.271Q35.542,25.375 34,25.375H30.5V34Q30.5,35.5 29.396,36.604Q28.292,37.708 26.792,37.708ZM5.958,34H26.792Q26.792,34 26.792,34Q26.792,34 26.792,34V21.542H5.958V34Q5.958,34 5.958,34Q5.958,34 5.958,34ZM30.5,21.667H34Q34,21.667 34,21.667Q34,21.667 34,21.667V9.208H13.208V14.583H26.833Q28.375,14.583 29.438,15.667Q30.5,16.75 30.5,18.25Z"/>
- </group>
-</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml b/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml
deleted file mode 100644
index ea0fbb0e5d33..000000000000
--- a/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="32.0dp"
- android:viewportWidth="32.0"
- android:viewportHeight="32.0"
->
- <group android:scaleX="0.5"
- android:scaleY="0.5"
- android:translateX="6.0"
- android:translateY="6.0">
- <path
- android:fillColor="@android:color/black"
- android:pathData="M18.167,21.875H29.833V10.208H18.167ZM7.875,35.833Q6.375,35.833 5.271,34.729Q4.167,33.625 4.167,32.125V7.875Q4.167,6.375 5.271,5.271Q6.375,4.167 7.875,4.167H32.125Q33.625,4.167 34.729,5.271Q35.833,6.375 35.833,7.875V32.125Q35.833,33.625 34.729,34.729Q33.625,35.833 32.125,35.833ZM7.875,32.125H32.125Q32.125,32.125 32.125,32.125Q32.125,32.125 32.125,32.125V7.875Q32.125,7.875 32.125,7.875Q32.125,7.875 32.125,7.875H7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875V32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125ZM7.875,7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875V32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125V7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875Z"/>
- </group>
-</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml b/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml
deleted file mode 100644
index c55cbe2d054c..000000000000
--- a/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="32.0dp"
- android:viewportWidth="32.0"
- android:viewportHeight="32.0"
->
- <group android:scaleX="0.5"
- android:scaleY="0.5"
- android:translateX="6.0"
- android:translateY="6.0">
- <path
- android:fillColor="@android:color/black"
- android:pathData="M34.042,14.625V9.333Q34.042,9.333 34.042,9.333Q34.042,9.333 34.042,9.333H28.708V5.708H33.917Q35.458,5.708 36.562,6.833Q37.667,7.958 37.667,9.458V14.625ZM2.375,14.625V9.458Q2.375,7.958 3.479,6.833Q4.583,5.708 6.125,5.708H11.292V9.333H6Q6,9.333 6,9.333Q6,9.333 6,9.333V14.625ZM28.708,34.25V30.667H34.042Q34.042,30.667 34.042,30.667Q34.042,30.667 34.042,30.667V25.333H37.667V30.542Q37.667,32 36.562,33.125Q35.458,34.25 33.917,34.25ZM6.125,34.25Q4.583,34.25 3.479,33.125Q2.375,32 2.375,30.542V25.333H6V30.667Q6,30.667 6,30.667Q6,30.667 6,30.667H11.292V34.25ZM9.333,27.292V12.667H30.708V27.292ZM12.917,23.708H27.125V16.25H12.917ZM12.917,23.708V16.25V23.708Z"/>
- </group>
-</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_more_button.xml b/libs/WindowManager/Shell/res/drawable/caption_more_button.xml
deleted file mode 100644
index 447df43dfddd..000000000000
--- a/libs/WindowManager/Shell/res/drawable/caption_more_button.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="32.0dp"
- android:viewportWidth="32.0"
- android:viewportHeight="32.0"
->
- <group android:scaleX="0.5"
- android:scaleY="0.5"
- android:translateX="6.0"
- android:translateY="6.0">
- <path
- android:fillColor="@android:color/black"
- android:pathData="M8.083,22.833Q6.917,22.833 6.104,22Q5.292,21.167 5.292,20Q5.292,18.833 6.125,18Q6.958,17.167 8.125,17.167Q9.292,17.167 10.125,18Q10.958,18.833 10.958,20Q10.958,21.167 10.125,22Q9.292,22.833 8.083,22.833ZM20,22.833Q18.833,22.833 18,22Q17.167,21.167 17.167,20Q17.167,18.833 18,18Q18.833,17.167 20,17.167Q21.167,17.167 22,18Q22.833,18.833 22.833,20Q22.833,21.167 22,22Q21.167,22.833 20,22.833ZM31.875,22.833Q30.708,22.833 29.875,22Q29.042,21.167 29.042,20Q29.042,18.833 29.875,18Q30.708,17.167 31.917,17.167Q33.083,17.167 33.896,18Q34.708,18.833 34.708,20Q34.708,21.167 33.875,22Q33.042,22.833 31.875,22.833Z"/>
- </group>
-</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_select_button.xml b/libs/WindowManager/Shell/res/drawable/caption_select_button.xml
deleted file mode 100644
index 8c60c8407174..000000000000
--- a/libs/WindowManager/Shell/res/drawable/caption_select_button.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="32.0dp"
- android:viewportWidth="32.0"
- android:viewportHeight="32.0"
->
- <group
- android:translateX="4.0"
- android:translateY="6.0">
- <path
- android:fillColor="@android:color/black"
- android:pathData="M13.7021 12.5833L16.5676 15.5L15.426 16.7333L12.526 13.8333L10.4426 15.9167V10.5H15.9176L13.7021 12.5833ZM13.8343 3.83333H15.501V5.5H13.8343V3.83333ZM15.501 2.16667H13.8343V0.566667C14.751 0.566667 15.501 1.33333 15.501 2.16667ZM10.501 0.5H12.1676V2.16667H10.501V0.5ZM13.8343 7.16667H15.501V8.83333H13.8343V7.16667ZM5.50098 15.5H3.83431V13.8333H5.50098V15.5ZM2.16764 5.5H0.500977V3.83333H2.16764V5.5ZM2.16764 0.566667V2.16667H0.500977C0.500977 1.33333 1.33431 0.566667 2.16764 0.566667ZM2.16764 12.1667H0.500977V10.5H2.16764V12.1667ZM5.50098 2.16667H3.83431V0.5H5.50098V2.16667ZM8.83431 2.16667H7.16764V0.5H8.83431V2.16667ZM8.83431 15.5H7.16764V13.8333H8.83431V15.5ZM2.16764 8.83333H0.500977V7.16667H2.16764V8.83333ZM2.16764 15.5667C1.25098 15.5667 0.500977 14.6667 0.500977 13.8333H2.16764V15.5667Z"/>
- </group>
-</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml b/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml
deleted file mode 100644
index c334a543a86a..000000000000
--- a/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="32.0dp"
- android:viewportWidth="32.0"
- android:viewportHeight="32.0"
->
- <group android:translateX="6.0"
- android:translateY="8.0">
- <path
- android:fillColor="@android:color/black"
- android:pathData="M18 14L13 14L13 2L18 2L18 14ZM20 14L20 2C20 0.9 19.1 -3.93402e-08 18 -8.74228e-08L13 -3.0598e-07C11.9 -3.54062e-07 11 0.9 11 2L11 14C11 15.1 11.9 16 13 16L18 16C19.1 16 20 15.1 20 14ZM7 14L2 14L2 2L7 2L7 14ZM9 14L9 2C9 0.9 8.1 -5.20166e-07 7 -5.68248e-07L2 -7.86805e-07C0.9 -8.34888e-07 -3.93403e-08 0.9 -8.74228e-08 2L-6.11959e-07 14C-6.60042e-07 15.1 0.9 16 2 16L7 16C8.1 16 9 15.1 9 14Z"/> </group>
-</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
index c6e634c6622c..4ee10f429b37 100644
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
@@ -17,6 +17,5 @@
<shape android:shape="rectangle"
xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/white" />
- <corners android:radius="@dimen/caption_menu_corner_radius" />
- <stroke android:width="1dp" android:color="#b3b3b3"/>
+ <corners android:radius="@dimen/desktop_mode_handle_menu_corner_radius" />
</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_collapse_menu_button.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_close.xml
index 166552dcb9e8..b7521d4200c0 100644
--- a/libs/WindowManager/Shell/res/drawable/caption_collapse_menu_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_close.xml
@@ -15,16 +15,12 @@
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24.0dp"
- android:height="24.0dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"
->
- <group android:scaleX="1.25"
- android:scaleY="1.75"
- android:translateY="6.0">
- <path
- android:fillColor="@android:color/black"
- android:pathData="M10.3937 6.93935L11.3337 5.99935L6.00033 0.666016L0.666992 5.99935L1.60699 6.93935L6.00033 2.55268"/>
- </group>
+ android:height="20dp"
+ android:tint="#000000"
+ android:viewportHeight="24"
+ android:viewportWidth="24"
+ android:width="20dp">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_desktop.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_desktop.xml
new file mode 100644
index 000000000000..e2b724b8abfd
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_desktop.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M16.667,15H3.333V5H16.667V15ZM16.667,16.667C17.583,16.667 18.333,15.917 18.333,15V5C18.333,4.083 17.583,3.333 16.667,3.333H3.333C2.417,3.333 1.667,4.083 1.667,5V15C1.667,15.917 2.417,16.667 3.333,16.667H16.667ZM15,6.667H9.167V8.333H13.333V10H15V6.667ZM5,9.167H12.5V13.333H5V9.167Z"
+ android:fillColor="#1C1C14"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_close_button.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_floating.xml
index e258564c70f7..b0ea98e5f788 100644
--- a/libs/WindowManager/Shell/res/drawable/caption_close_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_floating.xml
@@ -15,16 +15,12 @@
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="32.0dp"
- android:viewportWidth="32.0"
- android:viewportHeight="32.0"
->
- <group android:scaleX="0.5"
- android:scaleY="0.5"
- android:translateY="4.0">
- <path
- android:fillColor="#FFFF0000"
- android:pathData="M12.45,38.35 L9.65,35.55 21.2,24 9.65,12.45 12.45,9.65 24,21.2 35.55,9.65 38.35,12.45 26.8,24 38.35,35.55 35.55,38.35 24,26.8Z"/>
- </group>
+ android:width="21dp"
+ android:height="20dp"
+ android:viewportWidth="21"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M3.667,15H17V5H3.667V15ZM18.667,15C18.667,15.917 17.917,16.667 17,16.667H3.667C2.75,16.667 2,15.917 2,15V5C2,4.083 2.75,3.333 3.667,3.333H17C17.917,3.333 18.667,4.083 18.667,5V15ZM11.167,6.667H15.333V11.667H11.167V6.667Z"
+ android:fillColor="#1C1C14"
+ android:fillType="evenOdd"/>
</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_screenshot_button.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_fullscreen.xml
index 7c86888f5226..99e1d268c97c 100644
--- a/libs/WindowManager/Shell/res/drawable/caption_screenshot_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_fullscreen.xml
@@ -15,16 +15,12 @@
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="32.0dp"
- android:viewportWidth="32.0"
- android:viewportHeight="32.0"
->
- <group android:scaleX="0.5"
- android:scaleY="0.5"
- android:translateY="4.0">
- <path
- android:fillColor="@android:color/black"
- android:pathData="M10,38V28.35H13V35H19.65V38ZM10,19.65V10H19.65V13H13V19.65ZM28.35,38V35H35V28.35H38V38ZM35,19.65V13H28.35V10H38V19.65Z"/>
- </group>
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M3.333,15H16.667V5H3.333V15ZM18.333,15C18.333,15.917 17.583,16.667 16.667,16.667H3.333C2.417,16.667 1.667,15.917 1.667,15V5C1.667,4.083 2.417,3.333 3.333,3.333H16.667C17.583,3.333 18.333,4.083 18.333,5V15ZM5,6.667H15V13.333H5V6.667Z"
+ android:fillColor="#1C1C14"
+ android:fillType="evenOdd"/>
</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_screenshot.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_screenshot.xml
new file mode 100644
index 000000000000..79a91250bb78
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_screenshot.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M18.333,5.833L18.333,8.333L16.667,8.333L16.667,5.833L13.333,5.833L13.333,4.167L16.667,4.167C17.587,4.167 18.333,4.913 18.333,5.833Z"
+ android:fillColor="#1C1C14"/>
+ <path
+ android:pathData="M6.667,4.167L3.333,4.167C2.413,4.167 1.667,4.913 1.667,5.833L1.667,8.333L3.333,8.333L3.333,5.833L6.667,5.833L6.667,4.167Z"
+ android:fillColor="#1C1C14"/>
+ <path
+ android:pathData="M6.667,14.167L3.333,14.167L3.333,11.667L1.667,11.667L1.667,14.167C1.667,15.087 2.413,15.833 3.333,15.833L6.667,15.833L6.667,14.167Z"
+ android:fillColor="#1C1C14"/>
+ <path
+ android:pathData="M13.333,15.833L16.667,15.833C17.587,15.833 18.333,15.087 18.333,14.167L18.333,11.667L16.667,11.667L16.667,14.167L13.333,14.167L13.333,15.833Z"
+ android:fillColor="#1C1C14"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml
new file mode 100644
index 000000000000..7c4f49979455
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M15.701,14.583L18.567,17.5L17.425,18.733L14.525,15.833L12.442,17.917V12.5H17.917L15.701,14.583ZM15.833,5.833H17.5V7.5H15.833V5.833ZM17.5,4.167H15.833V2.567C16.75,2.567 17.5,3.333 17.5,4.167ZM12.5,2.5H14.167V4.167H12.5V2.5ZM15.833,9.167H17.5V10.833H15.833V9.167ZM7.5,17.5H5.833V15.833H7.5V17.5ZM4.167,7.5H2.5V5.833H4.167V7.5ZM4.167,2.567V4.167H2.5C2.5,3.333 3.333,2.567 4.167,2.567ZM4.167,14.167H2.5V12.5H4.167V14.167ZM7.5,4.167H5.833V2.5H7.5V4.167ZM10.833,4.167H9.167V2.5H10.833V4.167ZM10.833,17.5H9.167V15.833H10.833V17.5ZM4.167,10.833H2.5V9.167H4.167V10.833ZM4.167,17.567C3.25,17.567 2.5,16.667 2.5,15.833H4.167V17.567Z"
+ android:fillColor="#1C1C14"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_splitscreen.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_splitscreen.xml
new file mode 100644
index 000000000000..853ab60e046f
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_splitscreen.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="21dp"
+ android:height="20dp"
+ android:viewportWidth="21"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M17.333,15H4V5H17.333V15ZM17.333,16.667C18.25,16.667 19,15.917 19,15V5C19,4.083 18.25,3.333 17.333,3.333H4C3.083,3.333 2.333,4.083 2.333,5V15C2.333,15.917 3.083,16.667 4,16.667H17.333ZM9.833,6.667H5.667V13.333H9.833V6.667ZM11.5,6.667H15.667V13.333H11.5V6.667Z"
+ android:fillColor="#1C1C14"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
index 35562b650994..f6b21bad63f4 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -61,7 +61,7 @@
android:layout_width="32dp"
android:layout_height="32dp"
android:padding="4dp"
- android:contentDescription="@string/collapse_menu_text"
+ android:contentDescription="@string/expand_menu_text"
android:src="@drawable/ic_baseline_expand_more_24"
android:tint="@color/desktop_mode_caption_expand_button_dark"
android:background="@null"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
deleted file mode 100644
index ac13eaeda6f5..000000000000
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
+++ /dev/null
@@ -1,136 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/handle_menu"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:background="@drawable/desktop_mode_decor_menu_background"
- android:divider="?android:attr/dividerHorizontal"
- android:showDividers="middle"
- android:dividerPadding="18dip">
- <RelativeLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
- <ImageView
- android:id="@+id/application_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_margin="12dp"
- android:contentDescription="@string/app_icon_text"
- android:layout_alignParentStart="true"
- android:layout_centerVertical="true"/>
- <TextView
- android:id="@+id/application_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toEndOf="@+id/application_icon"
- android:layout_toStartOf="@+id/collapse_menu_button"
- android:textColor="#FF000000"
- android:layout_centerVertical="true"/>
- <Button
- android:id="@+id/collapse_menu_button"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_marginEnd="10dp"
- android:contentDescription="@string/collapse_menu_text"
- android:layout_alignParentEnd="true"
- android:background="@drawable/ic_baseline_expand_more_24"
- android:layout_centerVertical="true"/>
- </RelativeLayout>
- <LinearLayout
- android:id="@+id/windowing_mode_buttons"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_horizontal">
- <Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="0.5" />
- <ImageButton
- style="@style/CaptionWindowingButtonStyle"
- android:id="@+id/fullscreen_button"
- android:contentDescription="@string/fullscreen_text"
- android:src="@drawable/caption_fullscreen_button"
- android:scaleType="fitCenter"
- android:background="?android:selectableItemBackgroundBorderless"/>
- <Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="1" />
- <ImageButton
- style="@style/CaptionWindowingButtonStyle"
- android:id="@+id/split_screen_button"
- android:contentDescription="@string/split_screen_text"
- android:src="@drawable/caption_split_screen_button"
- android:scaleType="fitCenter"
- android:background="?android:selectableItemBackgroundBorderless"/>
- <Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="1" />
- <ImageButton
- style="@style/CaptionWindowingButtonStyle"
- android:id="@+id/floating_button"
- android:contentDescription="@string/float_button_text"
- android:src="@drawable/caption_floating_button"
- android:scaleType="fitCenter"
- android:background="?android:selectableItemBackgroundBorderless"/>
- <Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="1" />
- <ImageButton
- style="@style/CaptionWindowingButtonStyle"
- android:id="@+id/desktop_button"
- android:contentDescription="@string/desktop_text"
- android:src="@drawable/caption_desktop_button"
- android:scaleType="fitCenter"
- android:background="?android:selectableItemBackgroundBorderless"/>
- <Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="0.5" />
-
- </LinearLayout>
- <LinearLayout
- android:id="@+id/menu_buttons_misc"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
- <Button
- style="@style/CaptionMenuButtonStyle"
- android:id="@+id/screenshot_button"
- android:contentDescription="@string/screenshot_text"
- android:text="@string/screenshot_text"
- android:drawableStart="@drawable/caption_screenshot_button"/>
- <Button
- style="@style/CaptionMenuButtonStyle"
- android:id="@+id/select_button"
- android:contentDescription="@string/select_text"
- android:text="@string/select_text"
- android:drawableStart="@drawable/caption_select_button"/>
- <Button
- style="@style/CaptionMenuButtonStyle"
- android:id="@+id/close_button"
- android:contentDescription="@string/close_text"
- android:text="@string/close_text"
- android:drawableStart="@drawable/caption_close_button"
- android:textColor="#FFFF0000"/>
- </LinearLayout>
-</com.android.wm.shell.windowdecor.WindowDecorLinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
index 5ab159cdf264..1d6864c152c2 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
@@ -25,8 +25,9 @@
<ImageButton
android:id="@+id/caption_handle"
- android:layout_width="128dp"
+ android:layout_width="176dp"
android:layout_height="42dp"
+ android:paddingHorizontal="24dp"
android:contentDescription="@string/handle_text"
android:src="@drawable/decor_handle_dark"
tools:tint="@color/desktop_mode_caption_handle_bar_dark"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_app_info_pill.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_app_info_pill.xml
new file mode 100644
index 000000000000..167a003932d6
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_app_info_pill.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="@dimen/desktop_mode_handle_menu_width"
+ android:layout_height="@dimen/desktop_mode_handle_menu_app_info_pill_height"
+ android:orientation="horizontal"
+ android:background="@drawable/desktop_mode_decor_menu_background"
+ android:gravity="center_vertical">
+
+ <ImageView
+ android:id="@+id/application_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginStart="14dp"
+ android:layout_marginEnd="14dp"
+ android:contentDescription="@string/app_icon_text"/>
+
+ <TextView
+ android:id="@+id/application_name"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ tools:text="Gmail"
+ android:textColor="@color/desktop_mode_caption_menu_text_color"
+ android:textSize="14sp"
+ android:textFontWeight="500"
+ android:lineHeight="20dp"
+ android:textStyle="normal"
+ android:layout_weight="1"/>
+
+ <ImageButton
+ android:id="@+id/collapse_menu_button"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:padding="4dp"
+ android:layout_marginEnd="14dp"
+ android:layout_marginStart="14dp"
+ android:contentDescription="@string/collapse_menu_text"
+ android:src="@drawable/ic_baseline_expand_more_24"
+ android:rotation="180"
+ android:tint="@color/desktop_mode_caption_menu_buttons_color_inactive"
+ android:background="?android:selectableItemBackgroundBorderless"/>
+</LinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_more_actions_pill.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_more_actions_pill.xml
new file mode 100644
index 000000000000..40a4b53f3e1d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_more_actions_pill.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/desktop_mode_handle_menu_width"
+ android:layout_height="@dimen/desktop_mode_handle_menu_more_actions_pill_height"
+ android:orientation="vertical"
+ android:background="@drawable/desktop_mode_decor_menu_background">
+
+ <Button
+ android:id="@+id/screenshot_button"
+ android:contentDescription="@string/screenshot_text"
+ android:text="@string/screenshot_text"
+ android:drawableStart="@drawable/desktop_mode_ic_handle_menu_screenshot"
+ android:drawableTint="@color/desktop_mode_caption_menu_buttons_color_inactive"
+ style="@style/DesktopModeHandleMenuActionButton"/>
+
+ <Button
+ android:id="@+id/select_button"
+ android:contentDescription="@string/select_text"
+ android:text="@string/select_text"
+ android:drawableStart="@drawable/desktop_mode_ic_handle_menu_select"
+ android:drawableTint="@color/desktop_mode_caption_menu_buttons_color_inactive"
+ style="@style/DesktopModeHandleMenuActionButton"/>
+
+ <Button
+ android:id="@+id/close_button"
+ android:contentDescription="@string/close_text"
+ android:text="@string/close_text"
+ android:drawableStart="@drawable/desktop_mode_ic_handle_menu_close"
+ android:drawableTint="@color/desktop_mode_caption_menu_buttons_color_inactive"
+ style="@style/DesktopModeHandleMenuActionButton"/>
+
+</LinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_windowing_pill.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_windowing_pill.xml
new file mode 100644
index 000000000000..95283b9e214a
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_windowing_pill.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/desktop_mode_handle_menu_width"
+ android:layout_height="@dimen/desktop_mode_handle_menu_windowing_pill_height"
+ android:orientation="horizontal"
+ android:background="@drawable/desktop_mode_decor_menu_background"
+ android:gravity="center_vertical">
+
+ <ImageButton
+ android:id="@+id/fullscreen_button"
+ android:layout_marginEnd="4dp"
+ android:contentDescription="@string/fullscreen_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_fullscreen"
+ android:tint="@color/desktop_mode_caption_menu_buttons_color_inactive"
+ android:layout_weight="1"
+ style="@style/DesktopModeHandleMenuWindowingButton"/>
+
+ <ImageButton
+ android:id="@+id/split_screen_button"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:contentDescription="@string/split_screen_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_splitscreen"
+ android:tint="@color/desktop_mode_caption_menu_buttons_color_inactive"
+ android:layout_weight="1"
+ style="@style/DesktopModeHandleMenuWindowingButton"/>
+
+ <ImageButton
+ android:id="@+id/floating_button"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:contentDescription="@string/float_button_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_floating"
+ android:tint="@color/desktop_mode_caption_menu_buttons_color_inactive"
+ android:layout_weight="1"
+ style="@style/DesktopModeHandleMenuWindowingButton"/>
+
+ <ImageButton
+ android:id="@+id/desktop_button"
+ android:layout_marginStart="4dp"
+ android:contentDescription="@string/desktop_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_desktop"
+ android:tint="@color/desktop_mode_caption_menu_buttons_color_active"
+ android:layout_weight="1"
+ style="@style/DesktopModeHandleMenuWindowingButton"/>
+
+</LinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index 4a1635d71c57..4b885c278a7a 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -67,4 +67,7 @@
<color name="desktop_mode_caption_close_button_dark">#1C1C17</color>
<color name="desktop_mode_caption_app_name_light">#EFF1F2</color>
<color name="desktop_mode_caption_app_name_dark">#1C1C17</color>
+ <color name="desktop_mode_caption_menu_text_color">#191C1D</color>
+ <color name="desktop_mode_caption_menu_buttons_color_inactive">#191C1D</color>
+ <color name="desktop_mode_caption_menu_buttons_color_active">#00677E</color>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 3a8614aa6513..9049ed574ba5 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -372,20 +372,34 @@
<!-- Height of button (32dp) + 2 * margin (5dp each). -->
<dimen name="freeform_decor_caption_height">42dp</dimen>
- <!-- Width of buttons (32dp each) + padding (128dp total). -->
- <dimen name="freeform_decor_caption_menu_width">256dp</dimen>
+ <!-- The width of the handle menu in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_width">216dp</dimen>
- <dimen name="freeform_decor_caption_menu_height">250dp</dimen>
- <dimen name="freeform_decor_caption_menu_height_no_windowing_controls">210dp</dimen>
+ <!-- The height of the handle menu's "App Info" pill in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_app_info_pill_height">52dp</dimen>
- <dimen name="freeform_resize_handle">30dp</dimen>
+ <!-- The height of the handle menu's "Windowing" pill in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_windowing_pill_height">52dp</dimen>
- <dimen name="freeform_resize_corner">44dp</dimen>
+ <!-- The height of the handle menu's "More Actions" pill in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_more_actions_pill_height">156dp</dimen>
- <!-- The radius of the caption menu shadow. -->
- <dimen name="caption_menu_shadow_radius">4dp</dimen>
+ <!-- The top margin of the handle menu in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_margin_top">4dp</dimen>
+
+ <!-- The start margin of the handle menu in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_margin_start">6dp</dimen>
+
+ <!-- The margin between pills of the handle menu in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_pill_spacing_margin">2dp</dimen>
<!-- The radius of the caption menu corners. -->
- <dimen name="caption_menu_corner_radius">20dp</dimen>
+ <dimen name="desktop_mode_handle_menu_corner_radius">26dp</dimen>
+ <!-- The radius of the caption menu shadow. -->
+ <dimen name="desktop_mode_handle_menu_shadow_radius">2dp</dimen>
+
+ <dimen name="freeform_resize_handle">30dp</dimen>
+
+ <dimen name="freeform_resize_corner">44dp</dimen>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 395fdd1cfaa2..563fb4d88941 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -263,4 +263,6 @@
<string name="close_text">Close</string>
<!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] -->
<string name="collapse_menu_text">Close Menu</string>
+ <!-- Accessibility text for the handle menu open menu button [CHAR LIMIT=NONE] -->
+ <string name="expand_menu_text">Open Menu</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index d0782ad9b37e..8cad385e1d3f 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -30,25 +30,31 @@
<item name="android:activityCloseExitAnimation">@anim/forced_resizable_exit</item>
</style>
- <style name="CaptionButtonStyle">
- <item name="android:layout_width">32dp</item>
- <item name="android:layout_height">32dp</item>
- <item name="android:layout_margin">5dp</item>
- <item name="android:padding">4dp</item>
+ <style name="DesktopModeHandleMenuActionButton">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">52dp</item>
+ <item name="android:gravity">start|center_vertical</item>
+ <item name="android:padding">16dp</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:textFontWeight">500</item>
+ <item name="android:textColor">@color/desktop_mode_caption_menu_text_color</item>
+ <item name="android:drawablePadding">16dp</item>
+ <item name="android:background">?android:selectableItemBackground</item>
</style>
- <style name="CaptionWindowingButtonStyle">
- <item name="android:layout_width">40dp</item>
- <item name="android:layout_height">40dp</item>
- <item name="android:padding">4dp</item>
+ <style name="DesktopModeHandleMenuWindowingButton">
+ <item name="android:layout_width">48dp</item>
+ <item name="android:layout_height">48dp</item>
+ <item name="android:padding">14dp</item>
+ <item name="android:scaleType">fitCenter</item>
+ <item name="android:background">?android:selectableItemBackgroundBorderless</item>
</style>
- <style name="CaptionMenuButtonStyle" parent="@style/Widget.AppCompat.Button.Borderless">
- <item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">52dp</item>
- <item name="android:layout_marginStart">10dp</item>
+ <style name="CaptionButtonStyle">
+ <item name="android:layout_width">32dp</item>
+ <item name="android:layout_height">32dp</item>
+ <item name="android:layout_margin">5dp</item>
<item name="android:padding">4dp</item>
- <item name="android:gravity">start|center_vertical</item>
</style>
<style name="DockedDividerBackground">
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 5459094fd9ef..9eba5ecd36f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -1120,7 +1120,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
setDividerInteractive(!mImeShown || !mHasImeFocus || isFloating, true,
"onImeStartPositioning");
- return needOffset ? IME_ANIMATION_NO_ALPHA : 0;
+ return mTargetYOffset != mLastYOffset ? IME_ANIMATION_NO_ALPHA : 0;
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
index 49d40d3c2611..bca27a5c6636 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
@@ -86,6 +86,7 @@ public class TvPipCustomAction extends TvPipAction {
Bundle extras = new Bundle();
extras.putCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION,
mRemoteAction.getContentDescription());
+ extras.putBoolean(Notification.EXTRA_CONTAINS_CUSTOM_VIEW, true);
builder.addExtras(extras);
builder.setSemanticAction(isCloseAction()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index ebdaaa9aa4a9..22800ad8e8a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -66,13 +66,11 @@ class SplitScreenTransitions {
private final Transitions mTransitions;
private final Runnable mOnFinish;
- DismissTransition mPendingDismiss = null;
+ DismissSession mPendingDismiss = null;
TransitSession mPendingEnter = null;
- TransitSession mPendingRecent = null;
TransitSession mPendingResize = null;
private IBinder mAnimatingTransition = null;
- OneShotRemoteHandler mPendingRemoteHandler = null;
private OneShotRemoteHandler mActiveRemoteHandler = null;
private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish;
@@ -101,27 +99,30 @@ class SplitScreenTransitions {
mFinishCallback = finishCallback;
mAnimatingTransition = transition;
mFinishTransaction = finishTransaction;
- if (mPendingRemoteHandler != null) {
- mPendingRemoteHandler.startAnimation(transition, info, startTransaction,
- finishTransaction, mRemoteFinishCB);
- mActiveRemoteHandler = mPendingRemoteHandler;
- mPendingRemoteHandler = null;
- return;
+
+ final TransitSession pendingTransition = getPendingTransition(transition);
+ if (pendingTransition != null) {
+ if (pendingTransition.mCanceled) {
+ // The pending transition was canceled, so skip playing animation.
+ startTransaction.apply();
+ onFinish(null /* wct */, null /* wctCB */);
+ return;
+ }
+
+ if (pendingTransition.mRemoteHandler != null) {
+ pendingTransition.mRemoteHandler.startAnimation(transition, info, startTransaction,
+ finishTransaction, mRemoteFinishCB);
+ mActiveRemoteHandler = pendingTransition.mRemoteHandler;
+ return;
+ }
}
+
playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot, topRoot);
}
private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
@NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) {
- final TransitSession pendingTransition = getPendingTransition(transition);
- if (pendingTransition != null && pendingTransition.mCanceled) {
- // The pending transition was canceled, so skip playing animation.
- t.apply();
- onFinish(null /* wct */, null /* wctCB */);
- return;
- }
-
// Play some place-holder fade animations
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
@@ -212,7 +213,7 @@ class SplitScreenTransitions {
}
}
- void applyResizeTransition(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ void playResizeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback,
@@ -260,10 +261,6 @@ class SplitScreenTransitions {
return mPendingEnter != null && mPendingEnter.mTransition == transition;
}
- boolean isPendingRecent(IBinder transition) {
- return mPendingRecent != null && mPendingRecent.mTransition == transition;
- }
-
boolean isPendingDismiss(IBinder transition) {
return mPendingDismiss != null && mPendingDismiss.mTransition == transition;
}
@@ -276,8 +273,6 @@ class SplitScreenTransitions {
private TransitSession getPendingTransition(IBinder transition) {
if (isPendingEnter(transition)) {
return mPendingEnter;
- } else if (isPendingRecent(transition)) {
- return mPendingRecent;
} else if (isPendingDismiss(transition)) {
return mPendingDismiss;
} else if (isPendingResize(transition)) {
@@ -311,14 +306,8 @@ class SplitScreenTransitions {
@Nullable RemoteTransition remoteTransition,
@Nullable TransitionConsumedCallback consumedCallback,
@Nullable TransitionFinishedCallback finishedCallback) {
- mPendingEnter = new TransitSession(transition, consumedCallback, finishedCallback);
-
- if (remoteTransition != null) {
- // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
- mPendingRemoteHandler = new OneShotRemoteHandler(
- mTransitions.getMainExecutor(), remoteTransition);
- mPendingRemoteHandler.setTransition(transition);
- }
+ mPendingEnter = new TransitSession(
+ transition, consumedCallback, finishedCallback, remoteTransition);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ " deduced Enter split screen");
@@ -344,7 +333,7 @@ class SplitScreenTransitions {
/** Sets a transition to dismiss split. */
void setDismissTransition(@NonNull IBinder transition, @SplitScreen.StageType int dismissTop,
@SplitScreenController.ExitReason int reason) {
- mPendingDismiss = new DismissTransition(transition, reason, dismissTop);
+ mPendingDismiss = new DismissSession(transition, reason, dismissTop);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ " deduced Dismiss due to %s. toTop=%s",
@@ -372,32 +361,10 @@ class SplitScreenTransitions {
+ " deduced Resize split screen");
}
- void setRecentTransition(@NonNull IBinder transition,
- @Nullable RemoteTransition remoteTransition,
- @Nullable TransitionFinishedCallback finishCallback) {
- mPendingRecent = new TransitSession(transition, null /* consumedCb */, finishCallback);
-
- if (remoteTransition != null) {
- // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
- mPendingRemoteHandler = new OneShotRemoteHandler(
- mTransitions.getMainExecutor(), remoteTransition);
- mPendingRemoteHandler.setTransition(transition);
- }
-
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
- + " deduced Enter recent panel");
- }
-
void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) {
if (mergeTarget != mAnimatingTransition) return;
- if (isPendingEnter(transition) && isPendingRecent(mergeTarget)) {
- // Since there's an entering transition merged, recent transition no longer
- // need to handle entering split screen after the transition finished.
- mPendingRecent.setFinishedCallback(null);
- }
-
if (mActiveRemoteHandler != null) {
mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
} else {
@@ -425,19 +392,13 @@ class SplitScreenTransitions {
// An entering transition got merged, appends the rest operations to finish entering
// split screen.
mStageCoordinator.finishEnterSplitScreen(finishT);
- mPendingRemoteHandler = null;
}
mPendingEnter.onConsumed(aborted);
mPendingEnter = null;
- mPendingRemoteHandler = null;
} else if (isPendingDismiss(transition)) {
mPendingDismiss.onConsumed(aborted);
mPendingDismiss = null;
- } else if (isPendingRecent(transition)) {
- mPendingRecent.onConsumed(aborted);
- mPendingRecent = null;
- mPendingRemoteHandler = null;
} else if (isPendingResize(transition)) {
mPendingResize.onConsumed(aborted);
mPendingResize = null;
@@ -451,9 +412,6 @@ class SplitScreenTransitions {
if (isPendingEnter(mAnimatingTransition)) {
mPendingEnter.onFinished(wct, mFinishTransaction);
mPendingEnter = null;
- } else if (isPendingRecent(mAnimatingTransition)) {
- mPendingRecent.onFinished(wct, mFinishTransaction);
- mPendingRecent = null;
} else if (isPendingDismiss(mAnimatingTransition)) {
mPendingDismiss.onFinished(wct, mFinishTransaction);
mPendingDismiss = null;
@@ -462,7 +420,6 @@ class SplitScreenTransitions {
mPendingResize = null;
}
- mPendingRemoteHandler = null;
mActiveRemoteHandler = null;
mAnimatingTransition = null;
@@ -568,10 +525,11 @@ class SplitScreenTransitions {
}
/** Session for a transition and its clean-up callback. */
- static class TransitSession {
+ class TransitSession {
final IBinder mTransition;
TransitionConsumedCallback mConsumedCallback;
TransitionFinishedCallback mFinishedCallback;
+ OneShotRemoteHandler mRemoteHandler;
/** Whether the transition was canceled. */
boolean mCanceled;
@@ -579,10 +537,24 @@ class SplitScreenTransitions {
TransitSession(IBinder transition,
@Nullable TransitionConsumedCallback consumedCallback,
@Nullable TransitionFinishedCallback finishedCallback) {
+ this(transition, consumedCallback, finishedCallback, null /* remoteTransition */);
+ }
+
+ TransitSession(IBinder transition,
+ @Nullable TransitionConsumedCallback consumedCallback,
+ @Nullable TransitionFinishedCallback finishedCallback,
+ @Nullable RemoteTransition remoteTransition) {
mTransition = transition;
mConsumedCallback = consumedCallback;
mFinishedCallback = finishedCallback;
+ if (remoteTransition != null) {
+ // Wrapping the remote transition for ease-of-use. (OneShot handles all the binder
+ // linking/death stuff)
+ mRemoteHandler = new OneShotRemoteHandler(
+ mTransitions.getMainExecutor(), remoteTransition);
+ mRemoteHandler.setTransition(transition);
+ }
}
/** Sets transition consumed callback. */
@@ -621,11 +593,11 @@ class SplitScreenTransitions {
}
/** Bundled information of dismiss transition. */
- static class DismissTransition extends TransitSession {
+ class DismissSession extends TransitSession {
final int mReason;
final @SplitScreen.StageType int mDismissTop;
- DismissTransition(IBinder transition, int reason, int dismissTop) {
+ DismissSession(IBinder transition, int reason, int dismissTop) {
super(transition, null /* consumedCallback */, null /* finishedCallback */);
this.mReason = reason;
this.mDismissTop = dismissTop;
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 33cbdac67061..dd91a37039e4 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
@@ -2226,15 +2226,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
} else if (isOpening && inFullscreen) {
final int activityType = triggerTask.getActivityType();
if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
- if (request.getRemoteTransition() != null) {
- // starting recents/home, so don't handle this and let it fall-through to
- // the remote handler.
- return null;
- }
- // Need to use the old stuff for non-remote animations, otherwise we don't
- // exit split-screen.
- mSplitTransitions.setRecentTransition(transition, null /* remote */,
- this::onRecentsInSplitAnimationFinish);
+ // starting recents/home, so don't handle this and let it fall-through to
+ // the remote handler.
+ return null;
}
}
} else {
@@ -2363,8 +2357,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mSplitTransitions.isPendingEnter(transition)) {
shouldAnimate = startPendingEnterAnimation(
transition, info, startTransaction, finishTransaction);
- } else if (mSplitTransitions.isPendingRecent(transition)) {
- onRecentsInSplitAnimationStart(startTransaction);
} else if (mSplitTransitions.isPendingDismiss(transition)) {
shouldAnimate = startPendingDismissAnimation(
mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
@@ -2376,7 +2368,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return true;
}
} else if (mSplitTransitions.isPendingResize(transition)) {
- mSplitTransitions.applyResizeTransition(transition, info, startTransaction,
+ mSplitTransitions.playResizeAnimation(transition, info, startTransaction,
finishTransaction, finishCallback, mMainStage.mRootTaskInfo.token,
mSideStage.mRootTaskInfo.token, mMainStage.getSplitDecorManager(),
mSideStage.getSplitDecorManager());
@@ -2589,7 +2581,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
private boolean startPendingDismissAnimation(
- @NonNull SplitScreenTransitions.DismissTransition dismissTransition,
+ @NonNull SplitScreenTransitions.DismissSession dismissTransition,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
@NonNull SurfaceControl.Transaction finishT) {
prepareDismissAnimation(dismissTransition.mDismissTop, dismissTransition.mReason, info,
@@ -2626,7 +2618,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Call this when the recents animation during split-screen finishes. */
public void onRecentsInSplitAnimationFinish(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
+ SurfaceControl.Transaction finishT, TransitionInfo info) {
// Check if the recent transition is finished by returning to the current
// split, so we can restore the divider bar.
for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
@@ -2643,8 +2635,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
+ // TODO(b/275664132): Remove dismissing split screen here to fit in back-to-split support.
// Dismiss the split screen if it's not returning to split.
prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getTaskInfo() != null && TransitionUtil.isClosingType(change.getMode())) {
+ finishT.setCrop(change.getLeash(), null).hide(change.getLeash());
+ }
+ }
setSplitsVisible(false);
setDividerVisibility(false, finishT);
logExit(EXIT_REASON_UNKNOWN);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index d6f4d6daaa83..ead0bcd15c73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -267,9 +267,6 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
return;
}
sendStatusChanged();
- } else {
- throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
- + "\n mRootTaskInfo: " + mRootTaskInfo);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 646d55e4581c..36c9077a197b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -387,8 +387,15 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
return;
}
// Sync Transactions can't operate simultaneously with shell transition collection.
- // The transition animation (upon showing) will sync the location itself.
- if (isUsingShellTransitions() && mTaskViewTransitions.hasPending()) return;
+ if (isUsingShellTransitions()) {
+ if (mTaskViewTransitions.hasPending()) {
+ // There is already a transition in-flight. The window bounds will be synced
+ // once it is complete.
+ return;
+ }
+ mTaskViewTransitions.setTaskBounds(this, boundsOnScreen);
+ return;
+ }
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(mTaskToken, boundsOnScreen);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 9b995c5dc621..3b1ce49ebdc7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.taskview;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -23,6 +24,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.graphics.Rect;
import android.os.IBinder;
import android.util.Slog;
import android.view.SurfaceControl;
@@ -40,7 +42,7 @@ import java.util.ArrayList;
* Handles Shell Transitions that involve TaskView tasks.
*/
public class TaskViewTransitions implements Transitions.TransitionHandler {
- private static final String TAG = "TaskViewTransitions";
+ static final String TAG = "TaskViewTransitions";
private final ArrayList<TaskViewTaskController> mTaskViews = new ArrayList<>();
private final ArrayList<PendingTransition> mPending = new ArrayList<>();
@@ -197,6 +199,13 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
// visibility is reported in transition.
}
+ void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(taskView.getTaskInfo().token, boundsOnScreen);
+ mPending.add(new PendingTransition(TRANSIT_CHANGE, wct, taskView, null /* cookie */));
+ startNextTransition();
+ }
+
private void startNextTransition() {
if (mPending.isEmpty()) return;
final PendingTransition pending = mPending.get(0);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 586cab07c508..d52abf795152 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -187,17 +187,18 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
&& isOpeningType(request.getType())
&& request.getTriggerTask() != null
&& request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- && (request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_HOME
- || request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_RECENTS)
- && request.getRemoteTransition() != null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ && request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_HOME) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a going-home request while "
+ "Split-Screen is active, so treat it as Mixed.");
Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler =
mPlayer.dispatchRequest(transition, request, this);
if (handler == null) {
- android.util.Log.e(Transitions.TAG, " No handler for remote? This is unexpected"
- + ", there should at-least be RemoteHandler.");
- return null;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " Lean on the remote transition handler to fetch a proper remote via"
+ + " TransitionFilter");
+ handler = new Pair<>(
+ mPlayer.getRemoteTransitionHandler(),
+ new WindowContainerTransaction());
}
final MixedTransition mixed = new MixedTransition(
MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
@@ -515,7 +516,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
// If pair-to-pair switching, the post-recents clean-up isn't needed.
if (mixed.mAnimType != MixedTransition.ANIM_TYPE_PAIR_TO_PAIR) {
wct = wct != null ? wct : new WindowContainerTransaction();
- mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction);
+ mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction, info);
}
mSplitHandler.onTransitionAnimationComplete();
finishCallback.onTransitionFinished(wct, wctCB);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index beabf1842f8d..4284993a5448 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -324,6 +324,10 @@ public class Transitions implements RemoteCallable<Transitions> {
mRemoteTransitionHandler.removeFiltered(remoteTransition);
}
+ RemoteTransitionHandler getRemoteTransitionHandler() {
+ return mRemoteTransitionHandler;
+ }
+
/** Registers an observer on the lifecycle of transitions. */
public void registerObserver(@NonNull TransitionObserver observer) {
mObservers.add(observer);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index f943e52e59a2..c0dcd0b68c6f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -26,16 +26,20 @@ import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler
import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.util.DisplayMetrics;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.InputChannel;
@@ -541,9 +545,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
if (ev.getY() > statusBarHeight) {
if (DesktopModeStatus.isProto2Enabled()) {
mPauseRelayoutForTask = relevantDecor.mTaskInfo.taskId;
- mDesktopTasksController.ifPresent(
- c -> c.moveToDesktopWithAnimation(relevantDecor.mTaskInfo,
- getFreeformBounds(ev)));
+ centerAndMoveToDesktopWithAnimation(relevantDecor, ev);
} else if (DesktopModeStatus.isProto1Enabled()) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
}
@@ -596,25 +598,59 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
}
- private Rect getFreeformBounds(@NonNull MotionEvent ev) {
- final Rect endBounds = new Rect();
- final int finalWidth = (int) (FINAL_FREEFORM_SCALE
- * mDragToDesktopAnimationStartBounds.width());
- final int finalHeight = (int) (FINAL_FREEFORM_SCALE
- * mDragToDesktopAnimationStartBounds.height());
-
- endBounds.left = mDragToDesktopAnimationStartBounds.centerX() - finalWidth / 2
- + (int) (ev.getX() - mCaptionDragStartX);
- endBounds.right = endBounds.left + (int) (FINAL_FREEFORM_SCALE
- * mDragToDesktopAnimationStartBounds.width());
- endBounds.top = (int) (ev.getY()
- - ((FINAL_FREEFORM_SCALE - DRAG_FREEFORM_SCALE)
- * mDragToDesktopAnimationStartBounds.height() / 2));
- endBounds.bottom = endBounds.top + finalHeight;
-
+ /**
+ * Gets bounds of a scaled window centered relative to the screen bounds
+ * @param scale the amount to scale to relative to the Screen Bounds
+ */
+ private Rect calculateFreeformBounds(float scale) {
+ final Resources resources = mContext.getResources();
+ final DisplayMetrics metrics = resources.getDisplayMetrics();
+ final int screenWidth = metrics.widthPixels;
+ final int screenHeight = metrics.heightPixels;
+
+ final float adjustmentPercentage = (1f - scale) / 2;
+ final Rect endBounds = new Rect((int) (screenWidth * adjustmentPercentage),
+ (int) (screenHeight * adjustmentPercentage),
+ (int) (screenWidth * (adjustmentPercentage + scale)),
+ (int) (screenHeight * (adjustmentPercentage + scale)));
return endBounds;
}
+ /**
+ * Animates a window to the center, grows to freeform size, and transitions to Desktop Mode.
+ * @param relevantDecor the window decor of the task to be animated
+ * @param ev the motion event that triggers the animation
+ */
+ private void centerAndMoveToDesktopWithAnimation(DesktopModeWindowDecoration relevantDecor,
+ MotionEvent ev) {
+ ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+ animator.setDuration(FREEFORM_ANIMATION_DURATION);
+ final SurfaceControl sc = relevantDecor.mTaskSurface;
+ final Rect endBounds = calculateFreeformBounds(DRAG_FREEFORM_SCALE);
+ final Transaction t = mTransactionFactory.get();
+ final float diffX = endBounds.centerX() - ev.getX();
+ final float diffY = endBounds.top - ev.getY();
+ final float startingX = ev.getX() - DRAG_FREEFORM_SCALE
+ * mDragToDesktopAnimationStartBounds.width() / 2;
+
+ animator.addUpdateListener(animation -> {
+ final float animatorValue = (float) animation.getAnimatedValue();
+ final float x = startingX + diffX * animatorValue;
+ final float y = ev.getY() + diffY * animatorValue;
+ t.setPosition(sc, x, y);
+ t.apply();
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mDesktopTasksController.ifPresent(
+ c -> c.moveToDesktopWithAnimation(relevantDecor.mTaskInfo,
+ calculateFreeformBounds(FINAL_FREEFORM_SCALE)));
+ }
+ });
+ animator.start();
+ }
+
private void startAnimation(@NonNull DesktopModeWindowDecoration focusedDecor) {
mDragToDesktopValueAnimator = ValueAnimator.ofFloat(1f, DRAG_FREEFORM_SCALE);
mDragToDesktopValueAnimator.setDuration(FREEFORM_ANIMATION_DURATION);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index c45e3fc4e0c2..e08d40d76c16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -17,11 +17,15 @@
package com.android.wm.shell.windowdecor;
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 android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Point;
@@ -34,7 +38,8 @@ import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
-import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.window.WindowContainerTransaction;
@@ -71,17 +76,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private DragDetector mDragDetector;
private RelayoutParams mRelayoutParams = new RelayoutParams();
- private final int mCaptionMenuHeightId = R.dimen.freeform_decor_caption_menu_height;
- private final int mCaptionMenuHeightWithoutWindowingControlsId =
- R.dimen.freeform_decor_caption_menu_height_no_windowing_controls;
private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
new WindowDecoration.RelayoutResult<>();
- private AdditionalWindow mHandleMenu;
- private final int mHandleMenuWidthId = R.dimen.freeform_decor_caption_menu_width;
- private final int mHandleMenuShadowRadiusId = R.dimen.caption_menu_shadow_radius;
- private final int mHandleMenuCornerRadiusId = R.dimen.caption_menu_corner_radius;
- private PointF mHandleMenuPosition = new PointF();
+ private final PointF mHandleMenuAppInfoPillPosition = new PointF();
+ private final PointF mHandleMenuWindowingPillPosition = new PointF();
+ private final PointF mHandleMenuMoreActionsPillPosition = new PointF();
+
+ // Collection of additional windows that comprise the handle menu.
+ private AdditionalWindow mHandleMenuAppInfoPill;
+ private AdditionalWindow mHandleMenuWindowingPill;
+ private AdditionalWindow mHandleMenuMoreActionsPill;
private Drawable mAppIcon;
private CharSequence mAppName;
@@ -234,30 +239,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);
}
- private void setupHandleMenu() {
- final View menu = mHandleMenu.mWindowViewHost.getView();
- final View fullscreen = menu.findViewById(R.id.fullscreen_button);
- fullscreen.setOnClickListener(mOnCaptionButtonClickListener);
- final View desktop = menu.findViewById(R.id.desktop_button);
- desktop.setOnClickListener(mOnCaptionButtonClickListener);
- final ViewGroup windowingBtns = menu.findViewById(R.id.windowing_mode_buttons);
- windowingBtns.setVisibility(DesktopModeStatus.isProto1Enabled() ? View.GONE : View.VISIBLE);
- final View split = menu.findViewById(R.id.split_screen_button);
- split.setOnClickListener(mOnCaptionButtonClickListener);
- final View close = menu.findViewById(R.id.close_button);
- close.setOnClickListener(mOnCaptionButtonClickListener);
- final View collapse = menu.findViewById(R.id.collapse_menu_button);
- collapse.setOnClickListener(mOnCaptionButtonClickListener);
- menu.setOnTouchListener(mOnCaptionTouchListener);
-
- final ImageView appIcon = menu.findViewById(R.id.application_icon);
- final TextView appName = menu.findViewById(R.id.application_name);
- appIcon.setImageDrawable(mAppIcon);
- appName.setText(mAppName);
- }
-
boolean isHandleMenuActive() {
- return mHandleMenu != null;
+ return mHandleMenuAppInfoPill != null;
}
private void loadAppInfo() {
@@ -291,34 +274,142 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
final Resources resources = mDecorWindowContext.getResources();
final int captionWidth = mTaskInfo.getConfiguration()
.windowConfiguration.getBounds().width();
- final int menuWidth = loadDimensionPixelSize(resources, mHandleMenuWidthId);
- // The windowing controls are disabled in proto1.
- final int menuHeight = loadDimensionPixelSize(resources, DesktopModeStatus.isProto1Enabled()
- ? mCaptionMenuHeightWithoutWindowingControlsId : mCaptionMenuHeightId);
- final int shadowRadius = loadDimensionPixelSize(resources, mHandleMenuShadowRadiusId);
- final int cornerRadius = loadDimensionPixelSize(resources, mHandleMenuCornerRadiusId);
-
- final int x, y;
+ final int menuWidth = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_width);
+ final int shadowRadius = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_shadow_radius);
+ final int cornerRadius = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_corner_radius);
+ final int marginMenuTop = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_margin_top);
+ final int marginMenuStart = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_margin_start);
+ final int marginMenuSpacing = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_pill_spacing_margin);
+ final int appInfoPillHeight = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_app_info_pill_height);
+ final int windowingPillHeight = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_windowing_pill_height);
+ final int moreActionsPillHeight = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
+
+ final int menuX, menuY;
if (mRelayoutParams.mLayoutResId
== R.layout.desktop_mode_app_controls_window_decor) {
// Align the handle menu to the left of the caption.
- x = mRelayoutParams.mCaptionX - mResult.mDecorContainerOffsetX;
- y = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY;
+ menuX = mRelayoutParams.mCaptionX - mResult.mDecorContainerOffsetX + marginMenuStart;
+ menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + marginMenuTop;
} else {
// Position the handle menu at the center of the caption.
- x = mRelayoutParams.mCaptionX + (captionWidth / 2) - (menuWidth / 2)
+ menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (menuWidth / 2)
- mResult.mDecorContainerOffsetX;
- y = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY;
+ menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + marginMenuStart;
+ }
+
+ final int appInfoPillY = menuY;
+ createAppInfoPill(t, menuX, appInfoPillY, menuWidth, appInfoPillHeight, shadowRadius,
+ cornerRadius);
+
+ // Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
+ final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
+ final int windowingPillY = appInfoPillY + appInfoPillHeight + marginMenuSpacing;
+ if (shouldShowWindowingPill) {
+ createWindowingPill(t, menuX, windowingPillY, menuWidth, windowingPillHeight,
+ shadowRadius,
+ cornerRadius);
}
- mHandleMenuPosition.set(x, y);
- final String namePrefix = "Caption Menu";
- mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t, x, y,
- menuWidth, menuHeight, shadowRadius, cornerRadius);
+
+ final int moreActionsPillY;
+ if (shouldShowWindowingPill) {
+ // Take into account the windowing pill height and margins.
+ moreActionsPillY = windowingPillY + windowingPillHeight + marginMenuSpacing;
+ } else {
+ // Just start after the end of the app info pill + margins.
+ moreActionsPillY = appInfoPillY + appInfoPillHeight + marginMenuSpacing;
+ }
+ createMoreActionsPill(t, menuX, moreActionsPillY, menuWidth, moreActionsPillHeight,
+ shadowRadius, cornerRadius);
+
mSyncQueue.runInSync(transaction -> {
transaction.merge(t);
t.close();
});
- setupHandleMenu();
+ setupHandleMenu(shouldShowWindowingPill);
+ }
+
+ private void createAppInfoPill(SurfaceControl.Transaction t, int x, int y, int width,
+ int height, int shadowRadius, int cornerRadius) {
+ mHandleMenuAppInfoPillPosition.set(x, y);
+ mHandleMenuAppInfoPill = addWindow(
+ R.layout.desktop_mode_window_decor_handle_menu_app_info_pill,
+ "Menu's app info pill",
+ t, x, y, width, height, shadowRadius, cornerRadius);
+ }
+
+ private void createWindowingPill(SurfaceControl.Transaction t, int x, int y, int width,
+ int height, int shadowRadius, int cornerRadius) {
+ mHandleMenuWindowingPillPosition.set(x, y);
+ mHandleMenuWindowingPill = addWindow(
+ R.layout.desktop_mode_window_decor_handle_menu_windowing_pill,
+ "Menu's windowing pill",
+ t, x, y, width, height, shadowRadius, cornerRadius);
+ }
+
+ private void createMoreActionsPill(SurfaceControl.Transaction t, int x, int y, int width,
+ int height, int shadowRadius, int cornerRadius) {
+ mHandleMenuMoreActionsPillPosition.set(x, y);
+ mHandleMenuMoreActionsPill = addWindow(
+ R.layout.desktop_mode_window_decor_handle_menu_more_actions_pill,
+ "Menu's more actions pill",
+ t, x, y, width, height, shadowRadius, cornerRadius);
+ }
+
+ private void setupHandleMenu(boolean windowingPillShown) {
+ // App Info pill setup.
+ final View appInfoPillView = mHandleMenuAppInfoPill.mWindowViewHost.getView();
+ final ImageButton collapseBtn = appInfoPillView.findViewById(R.id.collapse_menu_button);
+ final ImageView appIcon = appInfoPillView.findViewById(R.id.application_icon);
+ final TextView appName = appInfoPillView.findViewById(R.id.application_name);
+ collapseBtn.setOnClickListener(mOnCaptionButtonClickListener);
+ appInfoPillView.setOnTouchListener(mOnCaptionTouchListener);
+ appIcon.setImageDrawable(mAppIcon);
+ appName.setText(mAppName);
+
+ // Windowing pill setup.
+ if (windowingPillShown) {
+ final View windowingPillView = mHandleMenuWindowingPill.mWindowViewHost.getView();
+ final ImageButton fullscreenBtn = windowingPillView.findViewById(
+ R.id.fullscreen_button);
+ final ImageButton splitscreenBtn = windowingPillView.findViewById(
+ R.id.split_screen_button);
+ final ImageButton floatingBtn = windowingPillView.findViewById(R.id.floating_button);
+ final ImageButton desktopBtn = windowingPillView.findViewById(R.id.desktop_button);
+ fullscreenBtn.setOnClickListener(mOnCaptionButtonClickListener);
+ splitscreenBtn.setOnClickListener(mOnCaptionButtonClickListener);
+ floatingBtn.setOnClickListener(mOnCaptionButtonClickListener);
+ desktopBtn.setOnClickListener(mOnCaptionButtonClickListener);
+ // The button corresponding to the windowing mode that the task is currently in uses a
+ // different color than the others.
+ final ColorStateList activeColorStateList = ColorStateList.valueOf(
+ mContext.getColor(R.color.desktop_mode_caption_menu_buttons_color_active));
+ final ColorStateList inActiveColorStateList = ColorStateList.valueOf(
+ mContext.getColor(R.color.desktop_mode_caption_menu_buttons_color_inactive));
+ fullscreenBtn.setImageTintList(
+ mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ ? activeColorStateList : inActiveColorStateList);
+ splitscreenBtn.setImageTintList(
+ mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
+ ? activeColorStateList : inActiveColorStateList);
+ floatingBtn.setImageTintList(mTaskInfo.getWindowingMode() == WINDOWING_MODE_PINNED
+ ? activeColorStateList : inActiveColorStateList);
+ desktopBtn.setImageTintList(mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ ? activeColorStateList : inActiveColorStateList);
+ }
+
+ // More Actions pill setup.
+ final View moreActionsPillView = mHandleMenuMoreActionsPill.mWindowViewHost.getView();
+ final Button closeBtn = moreActionsPillView.findViewById(R.id.close_button);
+ closeBtn.setOnClickListener(mOnCaptionButtonClickListener);
}
/**
@@ -326,8 +417,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
*/
void closeHandleMenu() {
if (!isHandleMenuActive()) return;
- mHandleMenu.releaseView();
- mHandleMenu = null;
+ mHandleMenuAppInfoPill.releaseView();
+ mHandleMenuAppInfoPill = null;
+ if (mHandleMenuWindowingPill != null) {
+ mHandleMenuWindowingPill.releaseView();
+ mHandleMenuWindowingPill = null;
+ }
+ mHandleMenuMoreActionsPill.releaseView();
+ mHandleMenuMoreActionsPill = null;
}
@Override
@@ -346,12 +443,29 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
// When this is called before the layout is fully inflated, width will be 0.
// Menu is not visible in this scenario, so skip the check if that is the case.
- if (mHandleMenu.mWindowViewHost.getView().getWidth() == 0) return;
+ if (mHandleMenuAppInfoPill.mWindowViewHost.getView().getWidth() == 0) return;
PointF inputPoint = offsetCaptionLocation(ev);
- if (!pointInView(mHandleMenu.mWindowViewHost.getView(),
- inputPoint.x - mHandleMenuPosition.x - mResult.mDecorContainerOffsetX,
- inputPoint.y - mHandleMenuPosition.y - mResult.mDecorContainerOffsetY)) {
+ final boolean pointInAppInfoPill = pointInView(
+ mHandleMenuAppInfoPill.mWindowViewHost.getView(),
+ inputPoint.x - mHandleMenuAppInfoPillPosition.x - mResult.mDecorContainerOffsetX,
+ inputPoint.y - mHandleMenuAppInfoPillPosition.y
+ - mResult.mDecorContainerOffsetY);
+ boolean pointInWindowingPill = false;
+ if (mHandleMenuWindowingPill != null) {
+ pointInWindowingPill = pointInView(mHandleMenuWindowingPill.mWindowViewHost.getView(),
+ inputPoint.x - mHandleMenuWindowingPillPosition.x
+ - mResult.mDecorContainerOffsetX,
+ inputPoint.y - mHandleMenuWindowingPillPosition.y
+ - mResult.mDecorContainerOffsetY);
+ }
+ final boolean pointInMoreActionsPill = pointInView(
+ mHandleMenuMoreActionsPill.mWindowViewHost.getView(),
+ inputPoint.x - mHandleMenuMoreActionsPillPosition.x
+ - mResult.mDecorContainerOffsetX,
+ inputPoint.y - mHandleMenuMoreActionsPillPosition.y
+ - mResult.mDecorContainerOffsetY);
+ if (!pointInAppInfoPill && !pointInWindowingPill && !pointInMoreActionsPill) {
closeHandleMenu();
}
}
@@ -408,14 +522,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
final View handle = caption.findViewById(R.id.caption_handle);
clickIfPointInView(new PointF(ev.getX(), ev.getY()), handle);
} else {
- final View menu = mHandleMenu.mWindowViewHost.getView();
- final int captionWidth = mTaskInfo.getConfiguration().windowConfiguration
- .getBounds().width();
- final int menuX = mRelayoutParams.mCaptionX + (captionWidth / 2)
- - (menu.getWidth() / 2);
- final PointF inputPoint = new PointF(ev.getX() - menuX, ev.getY());
- final View collapse = menu.findViewById(R.id.collapse_menu_button);
- if (clickIfPointInView(inputPoint, collapse)) return;
+ final View appInfoPill = mHandleMenuAppInfoPill.mWindowViewHost.getView();
+ final ImageButton collapse = appInfoPill.findViewById(R.id.collapse_menu_button);
+ // Translate the input point from display coordinates to the same space as the collapse
+ // button, meaning its parent (app info pill view).
+ final PointF inputPoint = new PointF(ev.getX() - mHandleMenuAppInfoPillPosition.x,
+ ev.getY() - mHandleMenuAppInfoPillPosition.y);
+ clickIfPointInView(inputPoint, collapse);
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index ed93045ec462..91846fafd1db 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -217,28 +217,37 @@ fun FlickerTest.splitAppLayerBoundsChanges(
) {
assertLayers {
if (landscapePosLeft) {
- this.splitAppLayerBoundsSnapToDivider(
+ splitAppLayerBoundsSnapToDivider(
+ component,
+ landscapePosLeft,
+ portraitPosTop,
+ scenario.endRotation
+ )
+ .then()
+ .isInvisible(component)
+ .then()
+ .splitAppLayerBoundsSnapToDivider(
component,
landscapePosLeft,
portraitPosTop,
scenario.endRotation
)
} else {
- this.splitAppLayerBoundsSnapToDivider(
- component,
- landscapePosLeft,
- portraitPosTop,
- scenario.endRotation
- )
- .then()
- .isInvisible(component)
- .then()
- .splitAppLayerBoundsSnapToDivider(
- component,
- landscapePosLeft,
- portraitPosTop,
- scenario.endRotation
- )
+ splitAppLayerBoundsSnapToDivider(
+ component,
+ landscapePosLeft,
+ portraitPosTop,
+ scenario.endRotation
+ )
+ .then()
+ .isInvisible(component)
+ .then()
+ .splitAppLayerBoundsSnapToDivider(
+ component,
+ landscapePosLeft,
+ portraitPosTop,
+ scenario.endRotation
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
new file mode 100644
index 000000000000..d01a0ee67f25
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.content.Context
+import android.system.helpers.CommandsHelper
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import com.android.wm.shell.flicker.BaseTest
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.LetterboxAppHelper
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.IFlickerTestData
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
+import org.junit.Assume
+import org.junit.Before
+import org.junit.runners.Parameterized
+
+abstract class BaseAppCompat(flicker: FlickerTest) : BaseTest(flicker) {
+ protected val context: Context = instrumentation.context
+ protected val letterboxApp = LetterboxAppHelper(instrumentation)
+ lateinit var cmdHelper: CommandsHelper
+ lateinit var letterboxStyle: HashMap<String, String>
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ setStartRotation()
+ letterboxApp.launchViaIntent(wmHelper)
+ setEndRotation()
+ }
+ }
+
+ @Before
+ fun before() {
+ cmdHelper = CommandsHelper.getInstance(instrumentation)
+ Assume.assumeTrue(tapl.isTablet && isIgnoreOrientationRequest())
+ }
+
+ private fun mapLetterboxStyle(): HashMap<String, String> {
+ val res = cmdHelper.executeShellCommand("wm get-letterbox-style")
+ val lines = res.lines()
+ val map = HashMap<String, String>()
+ for (line in lines) {
+ val keyValuePair = line.split(":")
+ if (keyValuePair.size == 2) {
+ val key = keyValuePair[0].trim()
+ map[key] = keyValuePair[1].trim()
+ }
+ }
+ return map
+ }
+
+ private fun isIgnoreOrientationRequest(): Boolean {
+ val res = cmdHelper.executeShellCommand("wm get-ignore-orientation-request")
+ return res != null && res.contains("true")
+ }
+
+ fun IFlickerTestData.setStartRotation() = setRotation(flicker.scenario.startRotation)
+
+ fun IFlickerTestData.setEndRotation() = setRotation(flicker.scenario.endRotation)
+
+ /** Checks that app entering letterboxed state have rounded corners */
+ fun assertLetterboxAppAtStartHasRoundedCorners() {
+ assumeLetterboxRoundedCornersEnabled()
+ flicker.assertLayersStart { this.hasRoundedCorners(letterboxApp) }
+ }
+
+ fun assertLetterboxAppAtEndHasRoundedCorners() {
+ assumeLetterboxRoundedCornersEnabled()
+ flicker.assertLayersEnd { this.hasRoundedCorners(letterboxApp) }
+ }
+
+ /** Only run on tests with config_letterboxActivityCornersRadius != 0 in devices */
+ private fun assumeLetterboxRoundedCornersEnabled() {
+ if (!::letterboxStyle.isInitialized) {
+ letterboxStyle = mapLetterboxStyle()
+ }
+ Assume.assumeTrue(letterboxStyle.getValue("Corner radius") != "0")
+ }
+
+ fun assertLetterboxAppVisibleAtStartAndEnd() {
+ flicker.appWindowIsVisibleAtStart(letterboxApp)
+ flicker.appWindowIsVisibleAtEnd(letterboxApp)
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.rotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.rotationTests()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
new file mode 100644
index 000000000000..c57100e44c17
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.platform.test.annotations.Postsubmit
+import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching app in size compat mode.
+ *
+ * To run this test: `atest WMShellFlickerTests:OpenAppInSizeCompatModeTest`
+ *
+ * Actions:
+ * ```
+ * Rotate non resizable portrait only app to opposite orientation to trigger size compat mode
+ * ```
+ * Notes:
+ * ```
+ * Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [BaseTest]
+ * ```
+ */
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class OpenAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker) {
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ setStartRotation()
+ letterboxApp.launchViaIntent(wmHelper)
+ }
+ transitions { setEndRotation() }
+ teardown { letterboxApp.exit(wmHelper) }
+ }
+
+ /**
+ * Windows maybe recreated when rotated. Checks that the focus does not change or if it does,
+ * focus returns to [letterboxApp]
+ */
+ @Postsubmit
+ @Test
+ fun letterboxAppFocusedAtEnd() = flicker.assertEventLog { focusChanges(letterboxApp.`package`) }
+
+ @Postsubmit
+ @Test
+ fun letterboxedAppHasRoundedCorners() = assertLetterboxAppAtEndHasRoundedCorners()
+
+ /**
+ * Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't
+ * flicker, and disappears before the transition is complete
+ */
+ @Postsubmit
+ @Test
+ fun rotationLayerAppearsAndVanishes() {
+ flicker.assertLayers {
+ this.isVisible(letterboxApp)
+ .then()
+ .isVisible(ComponentNameMatcher.ROTATION)
+ .then()
+ .isVisible(letterboxApp)
+ .isInvisible(ComponentNameMatcher.ROTATION)
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
new file mode 100644
index 000000000000..f111a8d62d83
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.platform.test.annotations.Postsubmit
+import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.helpers.WindowUtils
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test restarting app in size compat mode.
+ *
+ * To run this test: `atest WMShellFlickerTests:RestartAppInSizeCompatModeTest`
+ *
+ * Actions:
+ * ```
+ * Rotate app to opposite orientation to trigger size compat mode
+ * Press restart button and wait for letterboxed app to resize
+ * ```
+ * Notes:
+ * ```
+ * Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [BaseTest]
+ * ```
+ */
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class RestartAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker) {
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ transitions { letterboxApp.clickRestart(wmHelper) }
+ teardown { letterboxApp.exit(wmHelper) }
+ }
+
+ @Postsubmit
+ @Test
+ fun appVisibleAtStartAndEnd() = assertLetterboxAppVisibleAtStartAndEnd()
+
+ @Postsubmit
+ @Test
+ fun appLayerVisibilityChanges() {
+ flicker.assertLayers {
+ this.isVisible(letterboxApp)
+ .then()
+ .isInvisible(letterboxApp)
+ .then()
+ .isVisible(letterboxApp)
+ }
+ }
+
+ @Postsubmit
+ @Test
+ fun letterboxedAppHasRoundedCorners() = assertLetterboxAppAtStartHasRoundedCorners()
+
+ /** Checks that the visible region of [letterboxApp] is still within display bounds */
+ @Postsubmit
+ @Test
+ fun appWindowRemainInsideVisibleBounds() {
+ val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
+ flicker.assertLayersEnd { visibleRegion(letterboxApp).coversAtMost(displayBounds) }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index bd2ffc1a018d..2e81b30d2e9a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -71,7 +71,7 @@ class DismissSplitScreenByGoHome(flicker: FlickerTest) : SplitScreenBase(flicker
// TODO(b/245472831): Move back to presubmit after shell transitions landing.
@FlakyTest(bugId = 245472831)
@Test
- fun secondaryAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(primaryApp)
+ fun secondaryAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(secondaryApp)
// TODO(b/245472831): Move back to presubmit after shell transitions landing.
@FlakyTest(bugId = 245472831)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 7db5ecc484ad..17f174b2195f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.isShellTransitionsEnabled
@@ -86,16 +85,14 @@ class DragDividerToResize(flicker: FlickerTest) : SplitScreenBase(flicker) {
@Presubmit
@Test
- fun primaryAppLayerKeepVisible() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- flicker.layerKeepVisible(primaryApp)
- }
-
- @FlakyTest(bugId = 263213649)
- @Test
- fun primaryAppLayerKeepVisible_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- flicker.layerKeepVisible(primaryApp)
+ fun primaryAppLayerVisibilityChanges() {
+ flicker.assertLayers {
+ this.isVisible(secondaryApp)
+ .then()
+ .isInvisible(secondaryApp)
+ .then()
+ .isVisible(secondaryApp)
+ }
}
@Presubmit
@@ -110,7 +107,9 @@ class DragDividerToResize(flicker: FlickerTest) : SplitScreenBase(flicker) {
}
}
- @Presubmit @Test fun primaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(primaryApp)
+ @Presubmit
+ @Test
+ fun primaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(primaryApp)
@Presubmit
@Test
@@ -127,17 +126,6 @@ class DragDividerToResize(flicker: FlickerTest) : SplitScreenBase(flicker) {
)
}
- @FlakyTest(bugId = 263213649)
- @Test
- fun primaryAppBoundsChanges_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- flicker.splitAppLayerBoundsChanges(
- primaryApp,
- landscapePosLeft = true,
- portraitPosTop = false
- )
- }
-
@Presubmit
@Test
fun secondaryAppBoundsChanges() =
@@ -148,7 +136,7 @@ class DragDividerToResize(flicker: FlickerTest) : SplitScreenBase(flicker) {
)
/** {@inheritDoc} */
- @FlakyTest(bugId = 263213649)
+ @Presubmit
@Test
override fun entireScreenCovered() = super.entireScreenCovered()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
index 7901f7502e2c..62936e0f5ca8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
@@ -293,7 +293,7 @@ internal object SplitScreenUtils {
wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
?: error("Display not found")
val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
- dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3))
+ dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3), 2000)
wmHelper
.StateSyncBuilder()
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 df78d92a90c8..1b291468d6dc 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
@@ -278,7 +278,7 @@ public class SplitTransitionTests extends ShellTestCase {
// Make sure it cleans-up if recents doesn't restore
WindowContainerTransaction commitWCT = new WindowContainerTransaction();
mStageCoordinator.onRecentsInSplitAnimationFinish(commitWCT,
- mock(SurfaceControl.Transaction.class));
+ mock(SurfaceControl.Transaction.class), mock(TransitionInfo.class));
assertFalse(mStageCoordinator.isSplitScreenVisible());
}
@@ -317,7 +317,7 @@ public class SplitTransitionTests extends ShellTestCase {
mMainStage.onTaskAppeared(mMainChild, mock(SurfaceControl.class));
mSideStage.onTaskAppeared(mSideChild, mock(SurfaceControl.class));
mStageCoordinator.onRecentsInSplitAnimationFinish(restoreWCT,
- mock(SurfaceControl.Transaction.class));
+ mock(SurfaceControl.Transaction.class), mock(TransitionInfo.class));
assertTrue(mStageCoordinator.isSplitScreenVisible());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index 1a1bebd28aef..784ad9b006b6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -126,12 +126,6 @@ public final class StageTaskListenerTests extends ShellTestCase {
verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(true));
}
- @Test(expected = IllegalArgumentException.class)
- public void testUnknownTaskVanished() {
- final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
- mStageTaskListener.onTaskVanished(task);
- }
-
@Test
public void testTaskVanished() {
// With shell transitions, the transition manages status changes, so skip this test.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index c92d2f36d3a7..dfa3c1010eed 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -583,7 +583,7 @@ public class WindowDecorationTests extends ShellTestCase {
int cornerRadius = loadDimensionPixelSize(resources, mCaptionMenuCornerRadiusId);
String name = "Test Window";
WindowDecoration.AdditionalWindow additionalWindow =
- addWindow(R.layout.desktop_mode_decor_handle_menu, name,
+ addWindow(R.layout.desktop_mode_window_decor_handle_menu_app_info_pill, name,
mMockSurfaceControlAddWindowT,
x - mRelayoutResult.mDecorContainerOffsetX,
y - mRelayoutResult.mDecorContainerOffsetY,
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index f5a9850b31dd..9be77281eb11 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -831,7 +831,9 @@ public class Location implements Parcelable {
* will be present for any location.
*
* <ul>
- * <li> satellites - the number of satellites used to derive a GNSS fix
+ * <li> satellites - the number of satellites used to derive a GNSS fix. This key was deprecated
+ * in API 34 because the information can be obtained through more accurate means, such as by
+ * referencing {@link GnssStatus#usedInFix}.
* </ul>
*/
public @Nullable Bundle getExtras() {
diff --git a/media/OWNERS b/media/OWNERS
index 5f501372666b..4a6648e91af4 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -1,4 +1,5 @@
# Bug component: 1344
+atneya@google.com
elaurent@google.com
essick@google.com
etalvala@google.com
diff --git a/media/aidl/android/media/soundtrigger_middleware/OWNERS b/media/aidl/android/media/soundtrigger_middleware/OWNERS
index 01b2cb981bbb..1e41886fe716 100644
--- a/media/aidl/android/media/soundtrigger_middleware/OWNERS
+++ b/media/aidl/android/media/soundtrigger_middleware/OWNERS
@@ -1,2 +1 @@
-atneya@google.com
-elaurent@google.com
+include /media/java/android/media/soundtrigger/OWNERS
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 293d3d2861cd..e73cf87ba9f3 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -70,7 +70,7 @@ public class AudioSystem
throw new UnsupportedOperationException("Trying to instantiate AudioSystem");
}
- /* These values must be kept in sync with system/audio.h */
+ /* These values must be kept in sync with system/media/audio/include/system/audio-hal-enums.h */
/*
* If these are modified, please also update Settings.System.VOLUME_SETTINGS
* and attrs.xml and AudioManager.java.
@@ -961,7 +961,8 @@ public class AudioSystem
*/
//
- // audio device definitions: must be kept in sync with values in system/core/audio.h
+ // audio device definitions: must be kept in sync with values
+ // in system/media/audio/include/system/audio-hal-enums.h
//
/** @hide */
public static final int DEVICE_NONE = 0x0;
diff --git a/media/java/android/media/soundtrigger/OWNERS b/media/java/android/media/soundtrigger/OWNERS
index 01b2cb981bbb..85f7a4d605e6 100644
--- a/media/java/android/media/soundtrigger/OWNERS
+++ b/media/java/android/media/soundtrigger/OWNERS
@@ -1,2 +1,3 @@
+# Bug component: 48436
atneya@google.com
elaurent@google.com
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 82e5a7f0682a..c898fe5a9f2c 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -31,10 +31,10 @@
<string name="chooser_title">Choose a <xliff:g id="profile_name" example="watch">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%2$s</xliff:g>&lt;/strong&gt;</string>
<!-- Description of the privileges the application will get if associated with the companion device of WATCH profile (type) [CHAR LIMIT=NONE] -->
- <string name="summary_watch">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.</string>
+ <string name="summary_watch">This app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.</string>
<!-- Description of the privileges the application will get if associated with the companion device of WATCH profile for singleDevice(type) [CHAR LIMIT=NONE] -->
- <string name="summary_watch_single_device">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, and access these permissions:</string>
+ <string name="summary_watch_single_device">This app will be allowed to sync info, like the name of someone calling, and access these permissions</string>
<!-- ================= DEVICE_PROFILE_GLASSES ================= -->
@@ -48,7 +48,7 @@
<string name="summary_glasses_multi_device">This app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
<!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile for singleDevice(type) [CHAR LIMIT=NONE] -->
- <string name="summary_glasses_single_device">This app will be allowed to access these permissions on your phone:</string>
+ <string name="summary_glasses_single_device">This app will be allowed to access these permissions on your phone</string>
<!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
@@ -97,10 +97,10 @@
<string name="profile_name_generic">device</string>
<!-- Description of the privileges the application will get if associated with the companion device of unspecified profile (type) [CHAR LIMIT=NONE] -->
- <string name="summary_generic_single_device">This app will be able to sync info, like the name of someone calling, between your phone and <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>.</string>
+ <string name="summary_generic_single_device">This app will be able to sync info, like the name of someone calling, between your phone and <xliff:g id="device_name" example="My Watch">%1$s</xliff:g></string>
<!-- Description of the privileges the application will get if associated with the companion device of unspecified profile (type) [CHAR LIMIT=NONE] -->
- <string name="summary_generic">This app will be able to sync info, like the name of someone calling, between your phone and the chosen device.</string>
+ <string name="summary_generic">This app will be able to sync info, like the name of someone calling, between your phone and the chosen device</string>
<!-- ================= Buttons ================= -->
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 71ae578ec310..ae0882342be4 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -551,8 +551,7 @@ public class CompanionDeviceActivity extends FragmentActivity implements
summary = getHtmlFromResources(this, SUMMARIES.get(null), deviceName);
mConstraintList.setVisibility(View.GONE);
} else {
- summary = getHtmlFromResources(this, SUMMARIES.get(deviceProfile),
- getString(PROFILES_NAME.get(deviceProfile)), appLabel);
+ summary = getHtmlFromResources(this, SUMMARIES.get(deviceProfile));
mPermissionTypes.addAll(PERMISSION_TYPES.get(deviceProfile));
setupPermissionList();
}
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 0498a15269ce..e9b2e1041c22 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -42,7 +42,7 @@
<!-- Title for subsection of "Learn more about passkeys" screen about seamless transition. [CHAR LIMIT=80] -->
<string name="seamless_transition_title">Seamless transition</string>
<!-- Detail for subsection of "Learn more about passkeys" screen about seamless transition. [CHAR LIMIT=500] -->
- <string name="seamless_transition_detail">As we move towards a passwordless future, passwords will still be available alongside passkeys</string>
+ <string name="seamless_transition_detail">As we move towards a passwordless future, passwords will still be available alongside passkeys.</string>
<!-- This appears as the title of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
<string name="choose_provider_title">Choose where to save your <xliff:g id="createTypes" example="passkeys">%1$s</xliff:g></string>
<!-- This appears as the description body of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 7b98049b51c0..ed4cc959543b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -296,7 +296,11 @@ fun ProviderSelectionCard(
}
item { Divider(thickness = 24.dp, color = Color.Transparent) }
- item { BodyMediumText(text = stringResource(R.string.choose_provider_body)) }
+ item {
+ Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
+ BodyMediumText(text = stringResource(R.string.choose_provider_body))
+ }
+ }
item { Divider(thickness = 16.dp, color = Color.Transparent) }
item {
CredentialContainerCard {
@@ -444,8 +448,10 @@ fun MoreOptionsRowIntroCard(
}
item { Divider(thickness = 24.dp, color = Color.Transparent) }
item {
- BodyMediumText(text = stringResource(
- R.string.use_provider_for_all_description, entryInfo.userProviderDisplayName))
+ Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
+ BodyMediumText(text = stringResource(
+ R.string.use_provider_for_all_description, entryInfo.userProviderDisplayName))
+ }
}
item { Divider(thickness = 24.dp, color = Color.Transparent) }
item {
@@ -626,25 +632,33 @@ fun MoreAboutPasskeysIntroCard(
MoreAboutPasskeySectionHeader(
text = stringResource(R.string.passwordless_technology_title)
)
- BodyMediumText(text = stringResource(R.string.passwordless_technology_detail))
+ Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
+ BodyMediumText(text = stringResource(R.string.passwordless_technology_detail))
+ }
}
item {
MoreAboutPasskeySectionHeader(
text = stringResource(R.string.public_key_cryptography_title)
)
- BodyMediumText(text = stringResource(R.string.public_key_cryptography_detail))
+ Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
+ BodyMediumText(text = stringResource(R.string.public_key_cryptography_detail))
+ }
}
item {
MoreAboutPasskeySectionHeader(
text = stringResource(R.string.improved_account_security_title)
)
- BodyMediumText(text = stringResource(R.string.improved_account_security_detail))
+ Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
+ BodyMediumText(text = stringResource(R.string.improved_account_security_detail))
+ }
}
item {
MoreAboutPasskeySectionHeader(
text = stringResource(R.string.seamless_transition_title)
)
- BodyMediumText(text = stringResource(R.string.seamless_transition_detail))
+ Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
+ BodyMediumText(text = stringResource(R.string.seamless_transition_detail))
+ }
}
}
onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_MORE_ABOUT_PASSKEYS_INTRO)
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 04168ce6d79a..a3d632cfb82a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -940,7 +940,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
+ getName()
+ ", groupId="
+ mGroupId
- + ", member= " + mMemberDevices
+ ")";
}
@@ -1495,6 +1494,33 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
/**
+ * In order to show the preference for the whole group, we always set the main device as the
+ * first connected device in the coordinated set, and then switch the content of the main
+ * device and member devices.
+ *
+ * @param newMainDevice the new Main device which is from the previous main device's member
+ * list.
+ */
+ public void switchMemberDeviceContent(CachedBluetoothDevice newMainDevice) {
+ // Backup from main device
+ final BluetoothDevice tmpDevice = mDevice;
+ final short tmpRssi = mRssi;
+ final boolean tmpJustDiscovered = mJustDiscovered;
+ // Set main device from sub device
+ release();
+ mDevice = newMainDevice.mDevice;
+ mRssi = newMainDevice.mRssi;
+ mJustDiscovered = newMainDevice.mJustDiscovered;
+
+ // Set sub device from backup
+ newMainDevice.release();
+ newMainDevice.mDevice = tmpDevice;
+ newMainDevice.mRssi = tmpRssi;
+ newMainDevice.mJustDiscovered = tmpJustDiscovered;
+ fetchActiveDevices();
+ }
+
+ /**
* Get cached bluetooth icon with description
*/
public Pair<Drawable, String> getDrawableWithDescription() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index d191b1e1c0e6..7b4c86207a2a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -464,59 +464,6 @@ public class CachedBluetoothDeviceManager {
return !(mOngoingSetMemberPair == null) && mOngoingSetMemberPair.equals(device);
}
- /**
- * In order to show the preference for the whole group, we always set the main device as the
- * first connected device in the coordinated set, and then switch the relationship of the main
- * device and member devices.
- *
- * @param newMainDevice the new Main device which is from the previous main device's member
- * list.
- */
- public void switchRelationshipFromMemberToMain(CachedBluetoothDevice newMainDevice) {
- if (newMainDevice == null) {
- log("switchRelationshipFromMemberToMain: input is null");
- return;
- }
- log("switchRelationshipFromMemberToMain: CachedBluetoothDevice list: " + mCachedDevices);
-
- final CachedBluetoothDevice finalNewMainDevice = newMainDevice;
- int newMainGroupId = newMainDevice.getGroupId();
- CachedBluetoothDevice oldMainDevice = mCachedDevices.stream()
- .filter(cachedDevice -> !cachedDevice.equals(finalNewMainDevice)
- && cachedDevice.getGroupId() == newMainGroupId).findFirst().orElse(null);
- boolean hasMainDevice = oldMainDevice != null;
- Set<CachedBluetoothDevice> memberSet =
- hasMainDevice ? oldMainDevice.getMemberDevice() : null;
- boolean isMemberDevice = memberSet != null && memberSet.contains(newMainDevice);
- if (!hasMainDevice || !isMemberDevice) {
- log("switchRelationshipFromMemberToMain: "
- + newMainDevice.getDevice().getAnonymizedAddress()
- + " is not the member device.");
- return;
- }
-
- mCachedDevices.remove(oldMainDevice);
- // When both LE Audio devices are disconnected, receiving member device
- // connection. To switch content and dispatch to notify UI change
- mBtManager.getEventManager().dispatchDeviceRemoved(oldMainDevice);
-
- for (CachedBluetoothDevice memberDeviceItem : memberSet) {
- if (memberDeviceItem.equals(newMainDevice)) {
- continue;
- }
- newMainDevice.addMemberDevice(memberDeviceItem);
- }
- memberSet.clear();
- newMainDevice.addMemberDevice(oldMainDevice);
-
- mCachedDevices.add(newMainDevice);
- // It is necessary to do remove and add for updating the mapping on
- // preference and device
- mBtManager.getEventManager().dispatchDeviceAdded(newMainDevice);
- log("switchRelationshipFromMemberToMain: After change, CachedBluetoothDevice list: "
- + mCachedDevices);
- }
-
private void log(String msg) {
if (DEBUG) {
Log.d(TAG, msg);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index 814c395865b1..20a6cd8e09ce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -238,10 +238,14 @@ public class CsipDeviceManager {
mainDevice.refresh();
return true;
} else {
- final CachedBluetoothDeviceManager deviceManager =
- mBtManager.getCachedDeviceManager();
- deviceManager.switchRelationshipFromMemberToMain(cachedDevice);
- cachedDevice.refresh();
+ // When both LE Audio devices are disconnected, receiving member device
+ // connection. To switch content and dispatch to notify UI change
+ mBtManager.getEventManager().dispatchDeviceRemoved(mainDevice);
+ mainDevice.switchMemberDeviceContent(cachedDevice);
+ mainDevice.refresh();
+ // It is necessary to do remove and add for updating the mapping on
+ // preference and device
+ mBtManager.getEventManager().dispatchDeviceAdded(mainDevice);
return true;
}
}
@@ -262,10 +266,14 @@ public class CsipDeviceManager {
for (CachedBluetoothDevice device: memberSet) {
if (device.isConnected()) {
log("set device: " + device + " as the main device");
- final CachedBluetoothDeviceManager deviceManager =
- mBtManager.getCachedDeviceManager();
- deviceManager.switchRelationshipFromMemberToMain(device);
- device.refresh();
+ // Main device is disconnected and sub device is connected
+ // To copy data from sub device to main device
+ mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice);
+ cachedDevice.switchMemberDeviceContent(device);
+ cachedDevice.refresh();
+ // It is necessary to do remove and add for updating the mapping on
+ // preference and device
+ mBtManager.getEventManager().dispatchDeviceAdded(cachedDevice);
return true;
}
}
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 1791dce6021f..4b3820eb0444 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
@@ -604,87 +604,4 @@ public class CachedBluetoothDeviceManagerTest {
verify(mDevice2).setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
verify(mDevice2).createBond(BluetoothDevice.TRANSPORT_LE);
}
-
- @Test
- public void switchRelationshipFromMemberToMain_switchesMainDevice_switchesSuccessful() {
- doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice1);
- CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
- doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice2);
- doReturn(CAP_GROUP2).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice3);
- CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
- CachedBluetoothDevice cachedDevice3 = mCachedDeviceManager.addDevice(mDevice3);
- assertThat(mCachedDeviceManager.isSubDevice(mDevice1)).isFalse();
- assertThat(mCachedDeviceManager.isSubDevice(mDevice2)).isTrue();
- assertThat(mCachedDeviceManager.isSubDevice(mDevice3)).isFalse();
- assertThat(cachedDevice1.getMemberDevice().contains(cachedDevice2)).isTrue();
-
- mCachedDeviceManager.switchRelationshipFromMemberToMain(cachedDevice2);
-
- assertThat(mCachedDeviceManager.isSubDevice(mDevice1)).isTrue();
- assertThat(mCachedDeviceManager.isSubDevice(mDevice2)).isFalse();
- assertThat(mCachedDeviceManager.isSubDevice(mDevice3)).isFalse();
- assertThat(cachedDevice2.getMemberDevice().contains(cachedDevice1)).isTrue();
- }
-
- @Test
- public void switchRelationshipFromMemberToMain_moreMembersCase_switchesSuccessful() {
- doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice1);
- CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
- doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice2);
- doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice3);
- CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
- CachedBluetoothDevice cachedDevice3 = mCachedDeviceManager.addDevice(mDevice3);
- assertThat(cachedDevice1.getMemberDevice().contains(cachedDevice2)).isTrue();
- assertThat(cachedDevice1.getMemberDevice().contains(cachedDevice3)).isTrue();
-
- mCachedDeviceManager.switchRelationshipFromMemberToMain(cachedDevice2);
-
- assertThat(mCachedDeviceManager.isSubDevice(mDevice1)).isTrue();
- assertThat(mCachedDeviceManager.isSubDevice(mDevice2)).isFalse();
- assertThat(mCachedDeviceManager.isSubDevice(mDevice3)).isTrue();
- assertThat(cachedDevice2.getMemberDevice().contains(cachedDevice1)).isTrue();
- assertThat(cachedDevice2.getMemberDevice().contains(cachedDevice3)).isTrue();
- }
-
- @Test
- public void switchRelationshipFromMemberToMain_inputDeviceIsMainDevice_doesNotChangelist() {
- doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice1);
- CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
- doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice2);
- doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice3);
- CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
- CachedBluetoothDevice cachedDevice3 = mCachedDeviceManager.addDevice(mDevice3);
- Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
- assertThat(cachedDevice1.getMemberDevice().contains(cachedDevice2)).isTrue();
- assertThat(cachedDevice1.getMemberDevice().contains(cachedDevice3)).isTrue();
-
- mCachedDeviceManager.switchRelationshipFromMemberToMain(cachedDevice1);
-
- devices = mCachedDeviceManager.getCachedDevicesCopy();
- assertThat(devices).contains(cachedDevice1);
- assertThat(cachedDevice1.getMemberDevice().contains(cachedDevice2)).isTrue();
- assertThat(cachedDevice1.getMemberDevice().contains(cachedDevice3)).isTrue();
- }
-
- @Test
- public void switchRelationshipFromMemberToMain_inputDeviceNotInMemberList_doesNotChangelist() {
- doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice1);
- CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
- doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice2);
- doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice3);
- CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
- cachedDevice1.getMemberDevice().remove(cachedDevice2);
- CachedBluetoothDevice cachedDevice3 = mCachedDeviceManager.addDevice(mDevice3);
- Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
-
- assertThat(cachedDevice1.getMemberDevice().contains(cachedDevice2)).isFalse();
- assertThat(cachedDevice1.getMemberDevice().contains(cachedDevice3)).isTrue();
-
- mCachedDeviceManager.switchRelationshipFromMemberToMain(cachedDevice2);
-
- devices = mCachedDeviceManager.getCachedDevicesCopy();
- assertThat(devices).contains(cachedDevice1);
- assertThat(cachedDevice1.getMemberDevice().contains(cachedDevice2)).isFalse();
- assertThat(cachedDevice1.getMemberDevice().contains(cachedDevice3)).isTrue();
- }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index ff1af92d71fe..1c179f838586 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -1138,6 +1138,25 @@ public class CachedBluetoothDeviceTest {
}
@Test
+ public void switchMemberDeviceContent_switchMainDevice_switchesSuccessful() {
+ mCachedDevice.mRssi = RSSI_1;
+ mCachedDevice.mJustDiscovered = JUSTDISCOVERED_1;
+ mSubCachedDevice.mRssi = RSSI_2;
+ mSubCachedDevice.mJustDiscovered = JUSTDISCOVERED_2;
+ mCachedDevice.addMemberDevice(mSubCachedDevice);
+
+ mCachedDevice.switchMemberDeviceContent(mSubCachedDevice);
+
+ assertThat(mCachedDevice.mRssi).isEqualTo(RSSI_2);
+ assertThat(mCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_2);
+ assertThat(mCachedDevice.mDevice).isEqualTo(mSubDevice);
+ assertThat(mSubCachedDevice.mRssi).isEqualTo(RSSI_1);
+ assertThat(mSubCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_1);
+ assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
+ assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
+ }
+
+ @Test
public void isConnectedHearingAidDevice_isConnectedAshaHearingAidDevice_returnTrue() {
when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 11b4d79925c6..2143fc4db852 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -21,12 +21,21 @@
<!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=30] -->
<string name="keyguard_enter_your_pin">Enter your PIN</string>
+ <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=26] -->
+ <string name="keyguard_enter_pin">Enter PIN</string>
+
<!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=30] -->
<string name="keyguard_enter_your_pattern">Enter your pattern</string>
+ <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=26] -->
+ <string name="keyguard_enter_pattern">Draw pattern</string>
+
<!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=30] -->
<string name="keyguard_enter_your_password">Enter your password</string>
+ <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=26] -->
+ <string name="keyguard_enter_password">Enter password</string>
+
<!-- Shown in the lock screen when there is SIM card IO error. -->
<string name="keyguard_sim_error_message_short">Invalid Card.</string>
@@ -118,11 +127,104 @@
<!-- Message shown when user enters wrong pattern -->
<string name="kg_wrong_pattern">Wrong pattern</string>
+
+ <!-- Message shown when user enters wrong pattern [CHAR LIMIT=26] -->
+ <string name="kg_wrong_pattern_try_again">Wrong pattern. Try again.</string>
+
<!-- Message shown when user enters wrong password -->
<string name="kg_wrong_password">Wrong password</string>
+
+ <!-- Message shown when user enters wrong pattern [CHAR LIMIT=26] -->
+ <string name="kg_wrong_password_try_again">Wrong password. Try again.</string>
+
<!-- Message shown when user enters wrong PIN -->
<string name="kg_wrong_pin">Wrong PIN</string>
- <!-- Countdown message shown after too many failed unlock attempts -->
+
+ <!-- Message shown when user enters wrong PIN [CHAR LIMIT=26] -->
+ <string name="kg_wrong_pin_try_again">Wrong PIN. Try again.</string>
+
+ <!-- Message shown when user enters wrong PIN/password/pattern below the main message, for ex: "Wrong PIN. Try again" in line 1 and the following text in line 2. [CHAR LIMIT=52] -->
+ <string name="kg_wrong_input_try_fp_suggestion">Or unlock with fingerprint</string>
+
+ <!-- Message shown when user fingerprint is not recognized [CHAR LIMIT=26] -->
+ <string name="kg_fp_not_recognized">Fingerprint not recognized</string>
+
+ <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=26] -->
+ <string name="bouncer_face_not_recognized">Face not recognized</string>
+
+ <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=52] -->
+ <string name="kg_bio_try_again_or_pin">Try again or enter PIN</string>
+
+ <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=52] -->
+ <string name="kg_bio_try_again_or_password">Try again or enter password</string>
+
+ <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=52] -->
+ <string name="kg_bio_try_again_or_pattern">Try again or draw pattern</string>
+
+ <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=52] -->
+ <string name="kg_bio_too_many_attempts_pin">PIN is required after too many attempts</string>
+
+ <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=52] -->
+ <string name="kg_bio_too_many_attempts_password">Password is required after too many attempts</string>
+
+ <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=52] -->
+ <string name="kg_bio_too_many_attempts_pattern">Pattern is required after too many attempts</string>
+
+ <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26] -->
+ <string name="kg_unlock_with_pin_or_fp">Unlock with PIN or fingerprint</string>
+
+ <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26] -->
+ <string name="kg_unlock_with_password_or_fp">Unlock with password or fingerprint</string>
+
+ <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26] -->
+ <string name="kg_unlock_with_pattern_or_fp">Unlock with pattern or fingerprint</string>
+
+ <!-- Message shown when we are on bouncer after Device admin requested lockdown. [CHAR LIMIT=52] -->
+ <string name="kg_prompt_after_dpm_lock">For added security, device was locked by work policy</string>
+
+ <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=52] -->
+ <string name="kg_prompt_after_user_lockdown_pin">PIN is required after lockdown</string>
+
+ <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=52] -->
+ <string name="kg_prompt_after_user_lockdown_password">Password is required after lockdown</string>
+
+ <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=52] -->
+ <string name="kg_prompt_after_user_lockdown_pattern">Pattern is required after lockdown</string>
+
+ <!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update. [CHAR LIMIT=52] -->
+ <string name="kg_prompt_unattended_update">Update will install during inactive hours</string>
+
+ <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=52] -->
+ <string name="kg_prompt_pin_auth_timeout">Added security required. PIN not used for a while.</string>
+
+ <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=52] -->
+ <string name="kg_prompt_password_auth_timeout">Added security required. Password not used for a while.</string>
+
+ <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=52] -->
+ <string name="kg_prompt_pattern_auth_timeout">Added security required. Pattern not used for a while.</string>
+
+ <!-- Message shown when device hasn't been unlocked for a while. [CHAR LIMIT=52] -->
+ <string name="kg_prompt_auth_timeout">Added security required. Device wasn\u2019t unlocked for a while.</string>
+
+ <!-- Message shown when face unlock is not available after too many failed face authentication attempts. [CHAR LIMIT=52] -->
+ <string name="kg_face_locked_out">Can\u2019t unlock with face. Too many attempts.</string>
+
+ <!-- Message shown when fingerprint unlock isn't available after too many failed fingerprint authentication attempts. [CHAR LIMIT=52] -->
+ <string name="kg_fp_locked_out">Can\u2019t unlock with fingerprint. Too many attempts.</string>
+
+ <!-- Message shown when Trust Agent is disabled. [CHAR LIMIT=52] -->
+ <string name="kg_trust_agent_disabled">Trust agent is unavailable</string>
+
+ <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52] -->
+ <string name="kg_primary_auth_locked_out_pin">Too many attempts with incorrect PIN</string>
+
+ <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52] -->
+ <string name="kg_primary_auth_locked_out_pattern">Too many attempts with incorrect pattern</string>
+
+ <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52] -->
+ <string name="kg_primary_auth_locked_out_password">Too many attempts with incorrect password</string>
+
+ <!-- Countdown message shown after too many failed unlock attempts [CHAR LIMIT=26]-->
<string name="kg_too_many_failed_attempts_countdown">{count, plural,
=1 {Try again in # second.}
other {Try again in # seconds.}
@@ -194,14 +296,14 @@
<!-- Description of airplane mode -->
<string name="airplane_mode">Airplane mode</string>
- <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=80] -->
- <string name="kg_prompt_reason_restart_pattern">Pattern required after device restarts</string>
+ <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=52] -->
+ <string name="kg_prompt_reason_restart_pattern">Pattern is required after device restarts</string>
- <!-- An explanation text that the pin needs to be entered since the device has just been restarted. [CHAR LIMIT=80] -->
- <string name="kg_prompt_reason_restart_pin">PIN required after device restarts</string>
+ <!-- An explanation text that the pin needs to be entered since the device has just been restarted. [CHAR LIMIT=52] -->
+ <string name="kg_prompt_reason_restart_pin">PIN is required after device restarts</string>
- <!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=80] -->
- <string name="kg_prompt_reason_restart_password">Password required after device restarts</string>
+ <!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=52] -->
+ <string name="kg_prompt_reason_restart_password">Password is required after device restarts</string>
<!-- An explanation text that the pattern needs to be solved since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
<string name="kg_prompt_reason_timeout_pattern">For additional security, use pattern instead</string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0c0defadd8b0..2663ffb1fed9 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1407,13 +1407,13 @@
<dimen name="pulse_expansion_max_top_overshoot">32dp</dimen>
<!-- The drag amount required for the split shade to fully expand. -->
- <dimen name="split_shade_full_transition_distance">200dp</dimen>
+ <dimen name="split_shade_full_transition_distance">400dp</dimen>
<!--
The drag amount required for the scrim to fully fade in when expanding the split shade.
Currently setting it a little longer than the full shade transition distance, to avoid
having a state where the screen is fully black without any content showing.
-->
- <dimen name="split_shade_scrim_transition_distance">300dp</dimen>
+ <dimen name="split_shade_scrim_transition_distance">600dp</dimen>
<dimen name="people_space_widget_radius">28dp</dimen>
<dimen name="people_space_image_radius">20dp</dimen>
@@ -1553,6 +1553,9 @@
<dimen name="status_bar_user_chip_end_margin">12dp</dimen>
<dimen name="status_bar_user_chip_text_size">12sp</dimen>
+ <!-- System UI Dialog -->
+ <dimen name="dialog_title_text_size">24sp</dimen>
+
<!-- Internet panel related dimensions -->
<dimen name="internet_dialog_list_max_height">662dp</dimen>
<!-- The height of the WiFi network in Internet panel. -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e7be6fc80d1c..324ba02a7a46 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1270,6 +1270,11 @@
<!-- Label for button to go to sound settings screen [CHAR_LIMIT=30] -->
<string name="volume_panel_dialog_settings_button">Settings</string>
+ <!-- Title for notification after audio lowers -->
+ <string name="csd_lowered_title" product="default">Lowered to safer volume</string>
+ <!-- Message shown in notification after system lowers audio -->
+ <string name="csd_system_lowered_text" product="default">The volume has been high for longer than recommended</string>
+
<!-- content description for audio output chooser [CHAR LIMIT=NONE]-->
<!-- Screen pinning dialog title. -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index a3655c31fde9..8a86fd560655 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1041,7 +1041,7 @@
<style name="TextAppearance.Dialog.Title" parent="@android:style/TextAppearance.DeviceDefault.Large">
<item name="android:textColor">?android:attr/textColorPrimary</item>
- <item name="android:textSize">24sp</item>
+ <item name="android:textSize">@dimen/dialog_title_text_size</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:lineHeight">32sp</item>
<item name="android:gravity">center</item>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 1a572b729a6e..67874e13298c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -753,7 +753,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
case SimPuk:
// Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
- if (securityMode == SecurityMode.None && mLockPatternUtils.isLockScreenDisabled(
+ if (securityMode == SecurityMode.None || mLockPatternUtils.isLockScreenDisabled(
KeyguardUpdateMonitor.getCurrentUser())) {
finish = true;
eventSubtype = BOUNCER_DISMISS_SIM;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
index 1836ce857783..c9579d5e1356 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
@@ -21,6 +21,7 @@ import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.os.Bundle
import android.provider.Settings
+import android.util.TypedValue
import android.view.LayoutInflater
import android.widget.Button
import android.widget.SeekBar
@@ -49,8 +50,7 @@ class FontScalingDialog(
private lateinit var seekBarWithIconButtonsView: SeekBarWithIconButtonsView
private var lastProgress: Int = -1
- private val configuration: Configuration =
- Configuration(context.getResources().getConfiguration())
+ private val configuration: Configuration = Configuration(context.resources.configuration)
override fun onCreate(savedInstanceState: Bundle?) {
setTitle(R.string.font_scaling_dialog_title)
@@ -84,31 +84,45 @@ class FontScalingDialog(
seekBarWithIconButtonsView.setOnSeekBarChangeListener(
object : OnSeekBarChangeListener {
- override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
- if (progress != lastProgress) {
- if (!fontSizeHasBeenChangedFromTile) {
- backgroundExecutor.execute { updateSecureSettingsIfNeeded() }
- fontSizeHasBeenChangedFromTile = true
- }
-
- backgroundExecutor.execute { updateFontScale(strEntryValues[progress]) }
+ var isTrackingTouch = false
- lastProgress = progress
+ override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
+ if (!isTrackingTouch) {
+ // The seekbar progress is changed by icon buttons
+ changeFontSize(progress)
+ } else {
+ // Provide preview configuration for text instead of changing the system
+ // font scale before users release their finger from the seekbar.
+ createTextPreview(progress)
}
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
- // Do nothing
+ isTrackingTouch = true
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
- // Do nothing
+ isTrackingTouch = false
+ changeFontSize(seekBar.progress)
}
}
)
doneButton.setOnClickListener { dismiss() }
}
+ private fun changeFontSize(progress: Int) {
+ if (progress != lastProgress) {
+ if (!fontSizeHasBeenChangedFromTile) {
+ backgroundExecutor.execute { updateSecureSettingsIfNeeded() }
+ fontSizeHasBeenChangedFromTile = true
+ }
+
+ backgroundExecutor.execute { updateFontScale(strEntryValues[progress]) }
+
+ lastProgress = progress
+ }
+ }
+
private fun fontSizeValueToIndex(value: Float): Int {
var lastValue = strEntryValues[0].toFloat()
for (i in 1 until strEntryValues.size) {
@@ -153,6 +167,20 @@ class FontScalingDialog(
}
}
+ /** Provides font size preview for text before putting the final settings to the system. */
+ fun createTextPreview(index: Int) {
+ val previewConfig = Configuration(configuration)
+ previewConfig.fontScale = strEntryValues[index].toFloat()
+
+ val previewConfigContext = context.createConfigurationContext(previewConfig)
+ previewConfigContext.theme.setTo(context.theme)
+
+ title.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ previewConfigContext.resources.getDimension(R.dimen.dialog_title_text_size)
+ )
+ }
+
companion object {
private const val ON = "1"
private const val OFF = "0"
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
index 64211b5b138e..d15a2afa0d4a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -39,7 +39,7 @@ constructor(
private fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) =
mainExecutor.execute {
action?.let {
- if (event.tracking) {
+ if (event.tracking || event.expanded) {
Log.v(TAG, "Detected panel interaction, event: $event")
it.onPanelInteraction.run()
disable()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
index 3e7d81a9de90..063b41e8db0f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -390,11 +390,14 @@ constructor(
return true
}
- // Only pause auth if we're not on the keyguard AND we're not transitioning to doze
- // (ie: dozeAmount = 0f). For the UnlockedScreenOffAnimation, the statusBarState is
+ // Only pause auth if we're not on the keyguard AND we're not transitioning to doze.
+ // For the UnlockedScreenOffAnimation, the statusBarState is
// delayed. However, we still animate in the UDFPS affordance with the
- // mUnlockedScreenOffDozeAnimator.
- if (statusBarState != StatusBarState.KEYGUARD && lastDozeAmount == 0f) {
+ // unlockedScreenOffDozeAnimator.
+ if (
+ statusBarState != StatusBarState.KEYGUARD &&
+ !unlockedScreenOffAnimationController.isAnimationPlaying()
+ ) {
return true
}
if (isBouncerExpansionGreaterThan(.5f)) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 4b478cdca9f9..7c6a74864664 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -25,6 +25,7 @@ import static com.android.systemui.dreams.complication.ComplicationLayoutParams.
import android.animation.Animator;
import android.content.res.Resources;
+import android.graphics.Region;
import android.os.Handler;
import android.util.MathUtils;
import android.view.View;
@@ -223,6 +224,9 @@ public class DreamOverlayContainerViewController extends
mJitterStartTimeMillis = System.currentTimeMillis();
mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
+ final Region emptyRegion = Region.obtain();
+ mView.getRootSurfaceControl().setTouchableRegion(emptyRegion);
+ emptyRegion.recycle();
// Start dream entry animations. Skip animations for low light clock.
if (!mStateController.isLowLightActive()) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index edde4906c129..27f35db056e9 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -285,7 +285,7 @@ object Flags {
/** Enables Font Scaling Quick Settings tile */
// TODO(b/269341316): Tracking Bug
@JvmField
- val ENABLE_FONT_SCALING_TILE = unreleasedFlag(509, "enable_font_scaling_tile", teamfood = false)
+ val ENABLE_FONT_SCALING_TILE = unreleasedFlag(509, "enable_font_scaling_tile", teamfood = true)
/** Enables new QS Edit Mode visual refresh */
// TODO(b/269787742): Tracking Bug
@@ -697,10 +697,10 @@ object Flags {
// TODO(b/272036292): Tracking Bug
@JvmField
val LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION =
- unreleasedFlag(2602, "large_shade_granular_alpha_interpolation", teamfood = true)
+ releasedFlag(2602, "large_shade_granular_alpha_interpolation")
// TODO(b/272805037): Tracking Bug
@JvmField
- val ADVANCED_VPN_ENABLED = releasedFlag(2800, name = "AdvancedVpn__enable_feature",
- namespace = "vpn")
+ val ADVANCED_VPN_ENABLED = unreleasedFlag(2800, name = "AdvancedVpn__enable_feature",
+ namespace = "vpn", teamfood = true)
}
diff --git a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
new file mode 100644
index 000000000000..801b1652e487
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
@@ -0,0 +1,493 @@
+package com.android.systemui.graphics
+
+import android.annotation.AnyThread
+import android.annotation.DrawableRes
+import android.annotation.Px
+import android.annotation.SuppressLint
+import android.annotation.WorkerThread
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.content.res.Resources.NotFoundException
+import android.graphics.Bitmap
+import android.graphics.ImageDecoder
+import android.graphics.ImageDecoder.DecodeException
+import android.graphics.drawable.AdaptiveIconDrawable
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.util.Log
+import android.util.Size
+import androidx.core.content.res.ResourcesCompat
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import java.io.IOException
+import javax.inject.Inject
+import kotlin.math.min
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+/**
+ * Helper class to load images for SystemUI. It allows for memory efficient image loading with size
+ * restriction and attempts to use hardware bitmaps when sensible.
+ */
+@SysUISingleton
+class ImageLoader
+@Inject
+constructor(
+ private val defaultContext: Context,
+ @Background private val backgroundDispatcher: CoroutineDispatcher
+) {
+
+ /** Source of the image data. */
+ sealed interface Source
+
+ /**
+ * Load image from a Resource ID. If the resource is part of another package or if it requires
+ * tinting, pass in a correct [Context].
+ */
+ data class Res(@DrawableRes val resId: Int, val context: Context?) : Source {
+ constructor(@DrawableRes resId: Int) : this(resId, null)
+ }
+
+ /** Load image from a Uri. */
+ data class Uri(val uri: android.net.Uri) : Source {
+ constructor(uri: String) : this(android.net.Uri.parse(uri))
+ }
+
+ /** Load image from a [File]. */
+ data class File(val file: java.io.File) : Source {
+ constructor(path: String) : this(java.io.File(path))
+ }
+
+ /** Load image from an [InputStream]. */
+ data class InputStream(val inputStream: java.io.InputStream, val context: Context?) : Source {
+ constructor(inputStream: java.io.InputStream) : this(inputStream, null)
+ }
+
+ /**
+ * Loads passed [Source] on a background thread and returns the [Bitmap].
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints while keeping aspect
+ * ratio.
+ *
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Bitmap] or `null` if loading failed.
+ */
+ @AnyThread
+ suspend fun loadBitmap(
+ source: Source,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Bitmap? =
+ withContext(backgroundDispatcher) { loadBitmapSync(source, maxWidth, maxHeight, allocator) }
+
+ /**
+ * Loads passed [Source] synchronously and returns the [Bitmap].
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints while keeping aspect
+ * ratio.
+ *
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Bitmap] or `null` if loading failed.
+ */
+ @WorkerThread
+ fun loadBitmapSync(
+ source: Source,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Bitmap? {
+ return try {
+ loadBitmapSync(
+ toImageDecoderSource(source, defaultContext),
+ maxWidth,
+ maxHeight,
+ allocator
+ )
+ } catch (e: NotFoundException) {
+ Log.w(TAG, "Couldn't load resource $source", e)
+ null
+ }
+ }
+
+ /**
+ * Loads passed [ImageDecoder.Source] synchronously and returns the drawable.
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints (while keeping aspect
+ * ratio).
+ *
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Bitmap] or `null` if loading failed.
+ */
+ @WorkerThread
+ fun loadBitmapSync(
+ source: ImageDecoder.Source,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Bitmap? {
+ return try {
+ ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
+ configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
+ decoder.allocator = allocator
+ }
+ } catch (e: IOException) {
+ Log.w(TAG, "Failed to load source $source", e)
+ return null
+ } catch (e: DecodeException) {
+ Log.w(TAG, "Failed to decode source $source", e)
+ return null
+ }
+ }
+
+ /**
+ * Loads passed [Source] on a background thread and returns the [Drawable].
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints (while keeping aspect
+ * ratio).
+ *
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Drawable] or `null` if loading failed.
+ */
+ @AnyThread
+ suspend fun loadDrawable(
+ source: Source,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Drawable? =
+ withContext(backgroundDispatcher) {
+ loadDrawableSync(source, maxWidth, maxHeight, allocator)
+ }
+
+ /**
+ * Loads passed [Icon] on a background thread and returns the drawable.
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints (while keeping aspect
+ * ratio).
+ *
+ * @param context Alternate context to use for resource loading (for e.g. cross-process use)
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Drawable] or `null` if loading failed.
+ */
+ @AnyThread
+ suspend fun loadDrawable(
+ icon: Icon,
+ context: Context = defaultContext,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Drawable? =
+ withContext(backgroundDispatcher) {
+ loadDrawableSync(icon, context, maxWidth, maxHeight, allocator)
+ }
+
+ /**
+ * Loads passed [Source] synchronously and returns the drawable.
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints (while keeping aspect
+ * ratio).
+ *
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Drawable] or `null` if loading failed.
+ */
+ @WorkerThread
+ @SuppressLint("UseCompatLoadingForDrawables")
+ fun loadDrawableSync(
+ source: Source,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Drawable? {
+ return try {
+ loadDrawableSync(
+ toImageDecoderSource(source, defaultContext),
+ maxWidth,
+ maxHeight,
+ allocator
+ )
+ ?:
+ // If we have a resource, retry fallback using the "normal" Resource loading system.
+ // This will come into effect in cases like trying to load AnimatedVectorDrawable.
+ if (source is Res) {
+ val context = source.context ?: defaultContext
+ ResourcesCompat.getDrawable(context.resources, source.resId, context.theme)
+ } else {
+ null
+ }
+ } catch (e: NotFoundException) {
+ Log.w(TAG, "Couldn't load resource $source", e)
+ null
+ }
+ }
+
+ /**
+ * Loads passed [ImageDecoder.Source] synchronously and returns the drawable.
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints (while keeping aspect
+ * ratio).
+ *
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Drawable] or `null` if loading failed.
+ */
+ @WorkerThread
+ fun loadDrawableSync(
+ source: ImageDecoder.Source,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Drawable? {
+ return try {
+ ImageDecoder.decodeDrawable(source) { decoder, info, _ ->
+ configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
+ decoder.allocator = allocator
+ }
+ } catch (e: IOException) {
+ Log.w(TAG, "Failed to load source $source", e)
+ return null
+ } catch (e: DecodeException) {
+ Log.w(TAG, "Failed to decode source $source", e)
+ return null
+ }
+ }
+
+ /** Loads icon drawable while attempting to size restrict the drawable. */
+ @WorkerThread
+ fun loadDrawableSync(
+ icon: Icon,
+ context: Context = defaultContext,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Drawable? {
+ return when (icon.type) {
+ Icon.TYPE_URI,
+ Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
+ val source = ImageDecoder.createSource(context.contentResolver, icon.uri)
+ loadDrawableSync(source, maxWidth, maxHeight, allocator)
+ }
+ Icon.TYPE_RESOURCE -> {
+ val resources = resolveResourcesForIcon(context, icon)
+ resources?.let {
+ loadDrawableSync(
+ ImageDecoder.createSource(it, icon.resId),
+ maxWidth,
+ maxHeight,
+ allocator
+ )
+ }
+ // Fallback to non-ImageDecoder load if the attempt failed (e.g. the resource
+ // is a Vector drawable which ImageDecoder doesn't support.)
+ ?: icon.loadDrawable(context)
+ }
+ Icon.TYPE_BITMAP -> {
+ BitmapDrawable(context.resources, icon.bitmap)
+ }
+ Icon.TYPE_ADAPTIVE_BITMAP -> {
+ AdaptiveIconDrawable(null, BitmapDrawable(context.resources, icon.bitmap))
+ }
+ Icon.TYPE_DATA -> {
+ loadDrawableSync(
+ ImageDecoder.createSource(icon.dataBytes, icon.dataOffset, icon.dataLength),
+ maxWidth,
+ maxHeight,
+ allocator
+ )
+ }
+ else -> {
+ // We don't recognize this icon, just fallback.
+ icon.loadDrawable(context)
+ }
+ }?.let { drawable ->
+ // Icons carry tint which we need to propagate down to a Drawable.
+ tintDrawable(icon, drawable)
+ drawable
+ }
+ }
+
+ companion object {
+ const val TAG = "ImageLoader"
+
+ // 4096 is a reasonable default - most devices will support 4096x4096 texture size for
+ // Canvas rendering and by default we SystemUI has no need to render larger bitmaps.
+ // This prevents exceptions and crashes if the code accidentally loads larger Bitmap
+ // and then attempts to render it on Canvas.
+ // It can always be overridden by the parameters.
+ const val DEFAULT_MAX_SAFE_BITMAP_SIZE_PX = 4096
+
+ /**
+ * This constant signals that ImageLoader shouldn't attempt to resize the passed bitmap in a
+ * given dimension.
+ *
+ * Set both maxWidth and maxHeight to [DO_NOT_RESIZE] if you wish to prevent resizing.
+ */
+ const val DO_NOT_RESIZE = 0
+
+ /** Maps [Source] to [ImageDecoder.Source]. */
+ private fun toImageDecoderSource(source: Source, defaultContext: Context) =
+ when (source) {
+ is Res -> {
+ val context = source.context ?: defaultContext
+ ImageDecoder.createSource(context.resources, source.resId)
+ }
+ is File -> ImageDecoder.createSource(source.file)
+ is Uri -> ImageDecoder.createSource(defaultContext.contentResolver, source.uri)
+ is InputStream -> {
+ val context = source.context ?: defaultContext
+ ImageDecoder.createSource(context.resources, source.inputStream)
+ }
+ }
+
+ /**
+ * This sets target size on the image decoder to conform to the maxWidth / maxHeight
+ * parameters. The parameters are chosen to keep the existing drawable aspect ratio.
+ */
+ @AnyThread
+ private fun configureDecoderForMaximumSize(
+ decoder: ImageDecoder,
+ imgSize: Size,
+ @Px maxWidth: Int,
+ @Px maxHeight: Int
+ ) {
+ if (maxWidth == DO_NOT_RESIZE && maxHeight == DO_NOT_RESIZE) {
+ return
+ }
+
+ if (imgSize.width <= maxWidth && imgSize.height <= maxHeight) {
+ return
+ }
+
+ // Determine the scale factor for each dimension so it fits within the set constraint
+ val wScale =
+ if (maxWidth <= 0) {
+ 1.0f
+ } else {
+ maxWidth.toFloat() / imgSize.width.toFloat()
+ }
+
+ val hScale =
+ if (maxHeight <= 0) {
+ 1.0f
+ } else {
+ maxHeight.toFloat() / imgSize.height.toFloat()
+ }
+
+ // Scale down to the dimension that demands larger scaling (smaller scale factor).
+ // Use the same scale for both dimensions to keep the aspect ratio.
+ val scale = min(wScale, hScale)
+ if (scale < 1.0f) {
+ val targetWidth = (imgSize.width * scale).toInt()
+ val targetHeight = (imgSize.height * scale).toInt()
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Configured image size to $targetWidth x $targetHeight")
+ }
+
+ decoder.setTargetSize(targetWidth, targetHeight)
+ }
+ }
+
+ /**
+ * Attempts to retrieve [Resources] class required to load the passed icon. Icons can
+ * originate from other processes so we need to make sure we load them from the right
+ * package source.
+ *
+ * @return [Resources] to load the icon drawble or null if icon doesn't carry a resource or
+ * the resource package couldn't be resolved.
+ */
+ @WorkerThread
+ private fun resolveResourcesForIcon(context: Context, icon: Icon): Resources? {
+ if (icon.type != Icon.TYPE_RESOURCE) {
+ return null
+ }
+
+ val resources = icon.resources
+ if (resources != null) {
+ return resources
+ }
+
+ val resPackage = icon.resPackage
+ if (
+ resPackage == null || resPackage.isEmpty() || context.packageName.equals(resPackage)
+ ) {
+ return context.resources
+ }
+
+ if ("android" == resPackage) {
+ return Resources.getSystem()
+ }
+
+ val pm = context.packageManager
+ try {
+ val ai =
+ pm.getApplicationInfo(
+ resPackage,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES or
+ PackageManager.GET_SHARED_LIBRARY_FILES
+ )
+ if (ai != null) {
+ return pm.getResourcesForApplication(ai)
+ } else {
+ Log.w(TAG, "Failed to resolve application info for $resPackage")
+ }
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Failed to resolve resource package", e)
+ return null
+ }
+ return null
+ }
+
+ /** Applies tinting from [Icon] to the passed [Drawable]. */
+ @AnyThread
+ private fun tintDrawable(icon: Icon, drawable: Drawable) {
+ if (icon.hasTint()) {
+ drawable.mutate()
+ drawable.setTintList(icon.tintList)
+ drawable.setTintBlendMode(icon.tintBlendMode)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 5704f8861f0e..e204defb82b6 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -119,15 +119,6 @@ public class LogModule {
return factory.create("ShadeLog", 500, false);
}
- /** Provides a logging buffer for Shade height messages. */
- @Provides
- @SysUISingleton
- @ShadeHeightLog
- public static LogBuffer provideShadeHeightLogBuffer(LogBufferFactory factory) {
- return factory.create("ShadeHeightLog", 500 /* maxSize */);
- }
-
-
/** Provides a logging buffer for all logs related to managing notification sections. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 44c718f26a4a..e8ef612ae2cf 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -1732,6 +1732,11 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
final int gestureHeight = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_gesture_height);
final boolean handlingGesture = mEdgeBackGestureHandler.isHandlingGestures();
+ final InsetsFrameProvider mandatoryGestureProvider = new InsetsFrameProvider(
+ mInsetsSourceOwner, 0, WindowInsets.Type.mandatorySystemGestures());
+ if (handlingGesture) {
+ mandatoryGestureProvider.setInsetsSize(Insets.of(0, 0, 0, gestureHeight));
+ }
final int gestureInsetsLeft = handlingGesture
? mEdgeBackGestureHandler.getEdgeWidthLeft() + safeInsetsLeft : 0;
final int gestureInsetsRight = handlingGesture
@@ -1739,9 +1744,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
return new InsetsFrameProvider[] {
navBarProvider,
tappableElementProvider,
- new InsetsFrameProvider(
- mInsetsSourceOwner, 0, WindowInsets.Type.mandatorySystemGestures())
- .setInsetsSize(Insets.of(0, 0, 0, gestureHeight)),
+ mandatoryGestureProvider,
new InsetsFrameProvider(mInsetsSourceOwner, 0, WindowInsets.Type.systemGestures())
.setSource(InsetsFrameProvider.SOURCE_DISPLAY)
.setInsetsSize(Insets.of(gestureInsetsLeft, 0, 0, 0)),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 5e4f53181706..42536fef17aa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -30,6 +30,7 @@ import android.service.quicksettings.IQSService;
import android.service.quicksettings.Tile;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.SparseArrayMap;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -64,7 +65,7 @@ public class TileServices extends IQSService.Stub {
private static final String TAG = "TileServices";
private final ArrayMap<CustomTile, TileServiceManager> mServices = new ArrayMap<>();
- private final ArrayMap<ComponentName, CustomTile> mTiles = new ArrayMap<>();
+ private final SparseArrayMap<ComponentName, CustomTile> mTiles = new SparseArrayMap<>();
private final ArrayMap<IBinder, CustomTile> mTokenMap = new ArrayMap<>();
private final Context mContext;
private final Handler mMainHandler;
@@ -112,10 +113,11 @@ public class TileServices extends IQSService.Stub {
public TileServiceManager getTileWrapper(CustomTile tile) {
ComponentName component = tile.getComponent();
+ int userId = tile.getUser();
TileServiceManager service = onCreateTileService(component, mBroadcastDispatcher);
synchronized (mServices) {
mServices.put(tile, service);
- mTiles.put(component, tile);
+ mTiles.add(userId, component, tile);
mTokenMap.put(service.getToken(), tile);
}
// Makes sure binding only happens after the maps have been populated
@@ -135,7 +137,7 @@ public class TileServices extends IQSService.Stub {
service.handleDestroy();
mServices.remove(tile);
mTokenMap.remove(service.getToken());
- mTiles.remove(tile.getComponent());
+ mTiles.delete(tile.getUser(), tile.getComponent());
final String slot = getStatusBarIconSlotName(tile.getComponent());
mMainHandler.post(() -> mStatusBarIconController.removeIconForTile(slot));
}
@@ -188,9 +190,10 @@ public class TileServices extends IQSService.Stub {
private void requestListening(ComponentName component) {
synchronized (mServices) {
- CustomTile customTile = getTileForComponent(component);
+ int userId = mUserTracker.getUserId();
+ CustomTile customTile = getTileForUserAndComponent(userId, component);
if (customTile == null) {
- Log.d("TileServices", "Couldn't find tile for " + component);
+ Log.d(TAG, "Couldn't find tile for " + component + "(" + userId + ")");
return;
}
TileServiceManager service = mServices.get(customTile);
@@ -362,9 +365,9 @@ public class TileServices extends IQSService.Stub {
}
@Nullable
- private CustomTile getTileForComponent(ComponentName component) {
+ private CustomTile getTileForUserAndComponent(int userId, ComponentName component) {
synchronized (mServices) {
- return mTiles.get(component);
+ return mTiles.get(userId, component);
}
}
@@ -395,4 +398,5 @@ public class TileServices extends IQSService.Stub {
return -Integer.compare(left.getBindPriority(), right.getBindPriority());
}
};
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index d51a97f2ac52..a9af1a2457f9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -488,10 +488,6 @@ public class ScreenshotController {
});
}
- if (DEBUG_WINDOW) {
- Log.d(TAG, "setContentView: " + mScreenshotView);
- }
- setContentView(mScreenshotView);
// ignore system bar insets for the purpose of window layout
mWindow.getDecorView().setOnApplyWindowInsetsListener(
(v, insets) -> WindowInsets.CONSUMED);
@@ -790,10 +786,6 @@ public class ScreenshotController {
mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
mContext.getDrawable(R.drawable.overlay_badge_background), owner));
mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
- if (DEBUG_WINDOW) {
- Log.d(TAG, "setContentView: " + mScreenshotView);
- }
- setContentView(mScreenshotView);
// ignore system bar insets for the purpose of window layout
mWindow.getDecorView().setOnApplyWindowInsetsListener(
(v, insets) -> WindowInsets.CONSUMED);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
index fb2ddc15bab1..233667335b72 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
@@ -93,7 +93,7 @@ public class DebugDrawable extends Drawable {
drawDebugInfo(canvas, (int) mLockIconViewController.getTop(), Color.GRAY,
"mLockIconViewController.getTop()");
- if (mNotificationPanelViewController.getKeyguardShowing()) {
+ if (mNotificationPanelViewController.isKeyguardShowing()) {
// Notifications have the space between those two lines.
drawDebugInfo(canvas,
mNotificationStackScrollLayoutController.getTop()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index bf93c1020982..7b4685216111 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -249,13 +249,14 @@ import javax.inject.Provider;
import kotlinx.coroutines.CoroutineDispatcher;
@CentralSurfacesComponent.CentralSurfacesScope
-public final class NotificationPanelViewController implements Dumpable {
+public final class NotificationPanelViewController implements ShadeSurface, Dumpable {
public static final String TAG = NotificationPanelView.class.getSimpleName();
public static final float FLING_MAX_LENGTH_SECONDS = 0.6f;
public static final float FLING_SPEED_UP_FACTOR = 0.6f;
public static final float FLING_CLOSING_MAX_LENGTH_SECONDS = 0.6f;
public static final float FLING_CLOSING_SPEED_UP_FACTOR = 0.6f;
+ public static final int WAKEUP_ANIMATION_DELAY_MS = 250;
private static final boolean DEBUG_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private static final boolean SPEW_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
private static final boolean DEBUG_DRAWABLE = false;
@@ -278,6 +279,7 @@ public final class NotificationPanelViewController implements Dumpable {
private static final int NO_FIXED_DURATION = -1;
private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L;
private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L;
+
/**
* The factor of the usual high velocity that is needed in order to reach the maximum overshoot
* when flinging. A low value will make it that most flings will reach the maximum overshoot.
@@ -440,8 +442,6 @@ public final class NotificationPanelViewController implements Dumpable {
new KeyguardClockPositionAlgorithm.Result();
private boolean mIsExpanding;
- private String mHeaderDebugInfo;
-
/**
* Indicates drag starting height when swiping down or up on heads-up notifications.
* This usually serves as a threshold from when shade expansion should really start. Otherwise
@@ -457,6 +457,10 @@ public final class NotificationPanelViewController implements Dumpable {
private boolean mHeadsUpAnimatingAway;
private final FalsingManager mFalsingManager;
private final FalsingCollector mFalsingCollector;
+ private final ShadeHeadsUpTrackerImpl mShadeHeadsUpTracker = new ShadeHeadsUpTrackerImpl();
+ private final ShadeFoldAnimator mShadeFoldAnimator = new ShadeFoldAnimatorImpl();
+ private final ShadeNotificationPresenterImpl mShadeNotificationPresenter =
+ new ShadeNotificationPresenterImpl();
private boolean mShowIconsWhenExpanded;
private int mIndicationBottomPadding;
@@ -560,7 +564,6 @@ public final class NotificationPanelViewController implements Dumpable {
private final KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
private final KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
private float mMinExpandHeight;
- private final ShadeHeightLogger mShadeHeightLogger;
private boolean mPanelUpdateWhenAnimatorEnds;
private boolean mHasVibratedOnOpen = false;
private int mFixedDuration = NO_FIXED_DURATION;
@@ -605,6 +608,12 @@ public final class NotificationPanelViewController implements Dumpable {
private boolean mGestureWaitForTouchSlop;
private boolean mIgnoreXTouchSlop;
private boolean mExpandLatencyTracking;
+ /**
+ * Whether we're waking up and will play the delayed doze animation in
+ * {@link NotificationWakeUpCoordinator}. If so, we'll want to keep the clock centered until the
+ * delayed doze animation starts.
+ */
+ private boolean mWillPlayDelayedDozeAmountAnimation = false;
private final DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
private final OccludedToLockscreenTransitionViewModel mOccludedToLockscreenTransitionViewModel;
private final LockscreenToDreamingTransitionViewModel mLockscreenToDreamingTransitionViewModel;
@@ -627,7 +636,7 @@ public final class NotificationPanelViewController implements Dumpable {
() -> mKeyguardBottomArea.setVisibility(View.GONE);
private final Runnable mHeadsUpExistenceChangedRunnable = () -> {
setHeadsUpAnimatingAway(false);
- updatePanelExpansionAndVisibility();
+ updateExpansionAndVisibility();
};
private final Runnable mMaybeHideExpandedRunnable = () -> {
if (getExpandedFraction() == 0.0f) {
@@ -698,7 +707,6 @@ public final class NotificationPanelViewController implements Dumpable {
KeyguardUpdateMonitor keyguardUpdateMonitor,
MetricsLogger metricsLogger,
ShadeLogger shadeLogger,
- ShadeHeightLogger shadeHeightLogger,
ConfigurationController configurationController,
Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
@@ -768,7 +776,6 @@ public final class NotificationPanelViewController implements Dumpable {
mLockscreenGestureLogger = lockscreenGestureLogger;
mShadeExpansionStateManager = shadeExpansionStateManager;
mShadeLog = shadeLogger;
- mShadeHeightLogger = shadeHeightLogger;
mGutsManager = gutsManager;
mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
mOccludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel;
@@ -859,7 +866,7 @@ public final class NotificationPanelViewController implements Dumpable {
mMainDispatcher = mainDispatcher;
mAccessibilityManager = accessibilityManager;
mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
- setPanelAlpha(255, false /* animate */);
+ setAlpha(255, false /* animate */);
mCommandQueue = commandQueue;
mDisplayId = displayId;
mPulseExpansionHandler = pulseExpansionHandler;
@@ -1043,7 +1050,8 @@ public final class NotificationPanelViewController implements Dumpable {
mOnEmptySpaceClickListener);
mQsController.initNotificationStackScrollLayoutController();
mShadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
- addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp);
+ mShadeHeadsUpTracker.addTrackingHeadsUpListener(
+ mNotificationStackScrollLayoutController::setTrackingHeadsUp);
setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
initBottomArea();
@@ -1063,6 +1071,12 @@ public final class NotificationPanelViewController implements Dumpable {
requestScrollerTopPaddingUpdate(false /* animate */);
}
}
+
+ @Override
+ public void onDelayedDozeAmountAnimationRunning(boolean running) {
+ // On running OR finished, the animation is no longer waiting to play
+ setWillPlayDelayedDozeAmountAnimation(false);
+ }
});
mView.setRtlChangeListener(layoutDirection -> {
@@ -1203,6 +1217,7 @@ public final class NotificationPanelViewController implements Dumpable {
}
}
+ @Override
public void updateResources() {
final boolean newSplitShadeEnabled =
LargeScreenUtils.shouldUseSplitNotificationShade(mResources);
@@ -1383,7 +1398,7 @@ public final class NotificationPanelViewController implements Dumpable {
if (SPEW_LOGCAT) Log.d(TAG, "Skipping computeMaxKeyguardNotifications() by request");
}
- if (getKeyguardShowing() && !mKeyguardBypassController.getBypassEnabled()) {
+ if (isKeyguardShowing() && !mKeyguardBypassController.getBypassEnabled()) {
mNotificationStackScrollLayoutController.setMaxDisplayedNotifications(
mMaxAllowedKeyguardNotifications);
mNotificationStackScrollLayoutController.setKeyguardBottomPaddingForDebug(
@@ -1544,7 +1559,7 @@ public final class NotificationPanelViewController implements Dumpable {
updateClock();
}
- public KeyguardClockPositionAlgorithm.Result getClockPositionResult() {
+ KeyguardClockPositionAlgorithm.Result getClockPositionResult() {
return mClockPositionResult;
}
@@ -1655,15 +1670,32 @@ public final class NotificationPanelViewController implements Dumpable {
// overlap.
return true;
}
- if (hasPulsingNotifications()) {
+ if (mNotificationListContainer.hasPulsingNotifications()) {
// Pulsing notification appears on the right. Move clock left to avoid overlap.
return false;
}
+ if (mWillPlayDelayedDozeAmountAnimation) {
+ return true;
+ }
// "Visible" notifications are actually not visible on AOD (unless pulsing), so it is safe
// to center the clock without overlap.
return isOnAod();
}
+ /**
+ * Notify us that {@link NotificationWakeUpCoordinator} is going to play the doze wakeup
+ * animation after a delay. If so, we'll keep the clock centered until that animation starts.
+ */
+ public void setWillPlayDelayedDozeAmountAnimation(boolean willPlay) {
+ if (mWillPlayDelayedDozeAmountAnimation == willPlay) return;
+
+ mWillPlayDelayedDozeAmountAnimation = willPlay;
+ mWakeUpCoordinator.logDelayingClockWakeUpAnimation(willPlay);
+
+ // Once changing this value, see if we should move the clock.
+ positionClockAndNotifications();
+ }
+
private boolean isOnAod() {
return mDozing && mDozeParameters.getAlwaysOn();
}
@@ -1785,27 +1817,28 @@ public final class NotificationPanelViewController implements Dumpable {
}
}
- public void animateToFullShade(long delay) {
+ @Override
+ public void transitionToExpandedShade(long delay) {
mNotificationStackScrollLayoutController.goToFullShade(delay);
mView.requestLayout();
mAnimateNextPositionUpdate = true;
}
- /** Animate QS closing. */
- public void animateCloseQs(boolean animateAway) {
+ @Override
+ public void animateCollapseQs(boolean fullyCollapse) {
if (mSplitShadeEnabled) {
- collapsePanel(true, false, 1.0f);
+ collapse(true, false, 1.0f);
} else {
- mQsController.animateCloseQs(animateAway);
+ mQsController.animateCloseQs(fullyCollapse);
}
-
}
+ @Override
public void resetViews(boolean animate) {
mGutsManager.closeAndSaveGuts(true /* leavebehind */, true /* force */,
true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
if (animate && !isFullyCollapsed()) {
- animateCloseQs(true);
+ animateCollapseQs(true);
} else {
closeQsIfPossible();
}
@@ -1814,15 +1847,14 @@ public final class NotificationPanelViewController implements Dumpable {
mNotificationStackScrollLayoutController.resetScrollPosition();
}
- /** Collapses the panel. */
- public void collapsePanel(boolean animate, boolean delayed, float speedUpFactor) {
+ @Override
+ public void collapse(boolean animate, boolean delayed, float speedUpFactor) {
boolean waiting = false;
if (animate && !isFullyCollapsed()) {
collapse(delayed, speedUpFactor);
waiting = true;
} else {
resetViews(false /* animate */);
- mShadeHeightLogger.logFunctionCall("collapsePanel");
setExpandedFraction(0); // just in case
}
if (!waiting) {
@@ -1833,8 +1865,9 @@ public final class NotificationPanelViewController implements Dumpable {
}
}
+ @Override
public void collapse(boolean delayed, float speedUpFactor) {
- if (!canPanelBeCollapsed()) {
+ if (!canBeCollapsed()) {
return;
}
@@ -1843,7 +1876,7 @@ public final class NotificationPanelViewController implements Dumpable {
setShowShelfOnly(true);
}
debugLog("collapse: %s", this);
- if (canPanelBeCollapsed()) {
+ if (canBeCollapsed()) {
cancelHeightAnimator();
notifyExpandingStarted();
@@ -1874,11 +1907,13 @@ public final class NotificationPanelViewController implements Dumpable {
endClosing();
}
+ @Override
public void cancelAnimation() {
mView.animate().cancel();
}
- public void expandWithQs() {
+ @Override
+ public void expandToQs() {
if (mQsController.isExpansionEnabled()) {
mQsController.setExpandImmediate(true);
setShowShelfOnly(true);
@@ -1901,15 +1936,9 @@ public final class NotificationPanelViewController implements Dumpable {
}
}
- /**
- * Expand shade so that notifications are visible.
- * Non-split shade: just expanding shade or collapsing QS when they're expanded.
- * Split shade: only expanding shade, notifications are always visible
- *
- * Called when `adb shell cmd statusbar expand-notifications` is executed.
- */
- public void expandShadeToNotifications() {
- if (mSplitShadeEnabled && (isShadeFullyOpen() || isExpanding())) {
+ @Override
+ public void expandToNotifications() {
+ if (mSplitShadeEnabled && (isShadeFullyExpanded() || isExpanding())) {
return;
}
if (mQsController.getExpanded()) {
@@ -2046,7 +2075,7 @@ public final class NotificationPanelViewController implements Dumpable {
} else {
mQsController.cancelJankMonitoring();
}
- updatePanelExpansionAndVisibility();
+ updateExpansionAndVisibility();
mNotificationStackScrollLayoutController.setPanelFlinging(false);
}
@@ -2120,12 +2149,12 @@ public final class NotificationPanelViewController implements Dumpable {
}
/** Return whether a touch is near the gesture handle at the bottom of screen */
- public boolean isInGestureNavHomeHandleArea(float x, float y) {
+ boolean isInGestureNavHomeHandleArea(float x, float y) {
return mIsGestureNavigation && y > mView.getHeight() - mNavigationBarBottomHeight;
}
- /** Input focus transfer is about to happen. */
- public void startWaitingForOpenPanelGesture() {
+ @Override
+ public void startWaitingForExpandGesture() {
if (!isFullyCollapsed()) {
return;
}
@@ -2134,21 +2163,8 @@ public final class NotificationPanelViewController implements Dumpable {
updatePanelExpanded();
}
- /**
- * Called when this view is no longer waiting for input focus transfer.
- *
- * There are two scenarios behind this function call. First, input focus transfer
- * has successfully happened and this view already received synthetic DOWN event.
- * (mExpectingSynthesizedDown == false). Do nothing.
- *
- * Second, before input focus transfer finished, user may have lifted finger
- * in previous window and this window never received synthetic DOWN event.
- * (mExpectingSynthesizedDown == true).
- * In this case, we use the velocity to trigger fling event.
- *
- * @param velocity unit is in px / millis
- */
- public void stopWaitingForOpenPanelGesture(boolean cancel, final float velocity) {
+ @Override
+ public void stopWaitingForExpandGesture(boolean cancel, final float velocity) {
if (mExpectingSynthesizedDown) {
mExpectingSynthesizedDown = false;
if (cancel) {
@@ -2233,7 +2249,7 @@ public final class NotificationPanelViewController implements Dumpable {
* as the shade ends up in its half-expanded state (with QQS above), it is back at 100% scale.
* Without this, the shade would collapse, and stay squished.
*/
- public void adjustBackAnimationScale(float expansionFraction) {
+ void adjustBackAnimationScale(float expansionFraction) {
if (expansionFraction > 0.0f) { // collapsing
float animatedFraction = expansionFraction * mCurrentBackProgress;
applyBackScaling(animatedFraction);
@@ -2244,11 +2260,12 @@ public final class NotificationPanelViewController implements Dumpable {
}
//TODO(b/270981268): allow cancelling back animation mid-flight
- /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
+ @Override
public void onBackPressed() {
closeQsIfPossible();
}
- /** Sets back progress. */
+
+ @Override
public void onBackProgressed(float progressFraction) {
// TODO: non-linearly transform progress fraction into squish amount (ease-in, linear out)
mCurrentBackProgress = progressFraction;
@@ -2256,16 +2273,17 @@ public final class NotificationPanelViewController implements Dumpable {
}
/** Resets back progress. */
- public void resetBackTransformation() {
+ private void resetBackTransformation() {
mCurrentBackProgress = 0.0f;
applyBackScaling(0.0f);
}
- /** Scales multiple elements in tandem to achieve the illusion of the QS+Shade shrinking
- * as a single visual element (used by the Predictive Back Gesture preview animation).
- * fraction = 0 implies "no scaling", and 1 means "scale down to minimum size (90%)".
+ /**
+ * Scales multiple elements in tandem to achieve the illusion of the QS+Shade shrinking
+ * as a single visual element (used by the Predictive Back Gesture preview animation).
+ * fraction = 0 implies "no scaling", and 1 means "scale down to minimum size (90%)".
*/
- public void applyBackScaling(float fraction) {
+ private void applyBackScaling(float fraction) {
if (mNotificationContainerParent == null) {
return;
}
@@ -2273,15 +2291,6 @@ public final class NotificationPanelViewController implements Dumpable {
mNotificationContainerParent.applyBackScaling(scale, mSplitShadeEnabled);
mScrimController.applyBackScaling(scale);
}
- /** */
- public float getLockscreenShadeDragProgress() {
- // mTransitioningToFullShadeProgress > 0 means we're doing regular lockscreen to shade
- // transition. If that's not the case we should follow QS expansion fraction for when
- // user is pulling from the same top to go directly to expanded QS
- return mQsController.getTransitioningToFullShadeProgress() > 0
- ? mLockscreenShadeTransitionController.getQSDragProgress()
- : mQsController.computeExpansionFraction();
- }
String determineAccessibilityPaneTitle() {
if (mQsController != null && mQsController.isCustomizing()) {
@@ -2304,8 +2313,8 @@ public final class NotificationPanelViewController implements Dumpable {
}
/** Returns the topPadding of notifications when on keyguard not respecting QS expansion. */
- public int getKeyguardNotificationStaticPadding() {
- if (!getKeyguardShowing()) {
+ int getKeyguardNotificationStaticPadding() {
+ if (!isKeyguardShowing()) {
return 0;
}
if (!mKeyguardBypassController.getBypassEnabled()) {
@@ -2322,15 +2331,15 @@ public final class NotificationPanelViewController implements Dumpable {
}
}
- public boolean getKeyguardShowing() {
+ boolean isKeyguardShowing() {
return mBarState == KEYGUARD;
}
- public float getKeyguardNotificationTopPadding() {
+ float getKeyguardNotificationTopPadding() {
return mKeyguardNotificationTopPadding;
}
- public float getKeyguardNotificationBottomPadding() {
+ float getKeyguardNotificationBottomPadding() {
return mKeyguardNotificationBottomPadding;
}
@@ -2338,17 +2347,14 @@ public final class NotificationPanelViewController implements Dumpable {
mNotificationStackScrollLayoutController.updateTopPadding(
mQsController.calculateNotificationsTopPadding(mIsExpanding,
getKeyguardNotificationStaticPadding(), mExpandedFraction), animate);
- if (getKeyguardShowing()
+ if (isKeyguardShowing()
&& mKeyguardBypassController.getBypassEnabled()) {
// update the position of the header
mQsController.updateExpansion();
}
}
- /**
- * Set the alpha and translationY of the keyguard elements which only show on the lockscreen,
- * but not in shade locked / shade. This is used when dragging down to the full shade.
- */
+ @Override
public void setKeyguardTransitionProgress(float keyguardAlpha, int keyguardTranslationY) {
mKeyguardOnlyContentAlpha = Interpolators.ALPHA_IN.getInterpolation(keyguardAlpha);
mKeyguardOnlyTransitionTranslationY = keyguardTranslationY;
@@ -2360,17 +2366,13 @@ public final class NotificationPanelViewController implements Dumpable {
updateClock();
}
- /**
- * Sets the alpha value to be set on the keyguard status bar.
- *
- * @param alpha value between 0 and 1. -1 if the value is to be reset.
- */
+ @Override
public void setKeyguardStatusBarAlpha(float alpha) {
mKeyguardStatusBarViewController.setAlpha(alpha);
}
/** */
- public float getKeyguardOnlyContentAlpha() {
+ float getKeyguardOnlyContentAlpha() {
return mKeyguardOnlyContentAlpha;
}
@@ -2451,7 +2453,7 @@ public final class NotificationPanelViewController implements Dumpable {
float qsExpansionFraction;
if (mSplitShadeEnabled) {
qsExpansionFraction = 1;
- } else if (getKeyguardShowing()) {
+ } else if (isKeyguardShowing()) {
// On Keyguard, interpolate the QS expansion linearly to the panel expansion
qsExpansionFraction = expandedHeight / (getMaxPanelHeight());
} else {
@@ -2588,31 +2590,19 @@ public final class NotificationPanelViewController implements Dumpable {
}
setShowShelfOnly(false);
mQsController.setTwoFingerExpandPossible(false);
- updateTrackingHeadsUp(null);
+ mShadeHeadsUpTracker.updateTrackingHeadsUp(null);
mExpandingFromHeadsUp = false;
setPanelScrimMinFraction(0.0f);
// Reset status bar alpha so alpha can be calculated upon updating view state.
setKeyguardStatusBarAlpha(-1f);
}
- private void updateTrackingHeadsUp(@Nullable ExpandableNotificationRow pickedChild) {
- mTrackedHeadsUpNotification = pickedChild;
- for (int i = 0; i < mTrackingHeadsUpListeners.size(); i++) {
- Consumer<ExpandableNotificationRow> listener = mTrackingHeadsUpListeners.get(i);
- listener.accept(pickedChild);
- }
- }
-
- @Nullable
- public ExpandableNotificationRow getTrackedHeadsUpNotification() {
- return mTrackedHeadsUpNotification;
- }
-
private void setListening(boolean listening) {
mKeyguardStatusBarViewController.setBatteryListening(listening);
mQsController.setListening(listening);
}
+ @Override
public void expand(boolean animate) {
if (isFullyCollapsed() || isCollapsing()) {
mInstantExpanding = true;
@@ -2626,7 +2616,7 @@ public final class NotificationPanelViewController implements Dumpable {
if (mExpanding) {
notifyExpandingFinished();
}
- updatePanelExpansionAndVisibility();
+ updateExpansionAndVisibility();
// Wait for window manager to pickup the change, so we know the maximum height of the
// panel then.
this.mView.getViewTreeObserver().addOnGlobalLayoutListener(
@@ -2647,7 +2637,6 @@ public final class NotificationPanelViewController implements Dumpable {
mQsController.beginJankMonitoring(isFullyCollapsed());
fling(0 /* expand */);
} else {
- mShadeHeightLogger.logFunctionCall("expand");
setExpandedFraction(1f);
}
mInstantExpanding = false;
@@ -2666,7 +2655,8 @@ public final class NotificationPanelViewController implements Dumpable {
mTouchSlopExceeded = isTouchSlopExceeded;
}
- public void setOverExpansion(float overExpansion) {
+ @VisibleForTesting
+ void setOverExpansion(float overExpansion) {
if (overExpansion == mOverExpansion) {
return;
}
@@ -2706,20 +2696,20 @@ public final class NotificationPanelViewController implements Dumpable {
mTracking = true;
mTrackingStartedListener.onTrackingStarted();
notifyExpandingStarted();
- updatePanelExpansionAndVisibility();
+ updateExpansionAndVisibility();
mScrimController.onTrackingStarted();
if (mQsController.getFullyExpanded()) {
mQsController.setExpandImmediate(true);
setShowShelfOnly(true);
}
mNotificationStackScrollLayoutController.onPanelTrackingStarted();
- cancelPendingPanelCollapse();
+ cancelPendingCollapse();
}
private void onTrackingStopped(boolean expand) {
mFalsingCollector.onTrackingStopped();
mTracking = false;
- updatePanelExpansionAndVisibility();
+ updateExpansionAndVisibility();
if (expand) {
mNotificationStackScrollLayoutController.setOverScrollAmount(0.0f, true /* onTop */,
true /* animate */);
@@ -2805,6 +2795,7 @@ public final class NotificationPanelViewController implements Dumpable {
}
}
+ @Override
public void setIsLaunchAnimationRunning(boolean running) {
boolean wasRunning = mIsLaunchAnimationRunning;
mIsLaunchAnimationRunning = running;
@@ -2829,6 +2820,7 @@ public final class NotificationPanelViewController implements Dumpable {
}
}
+ @Override
public void onScreenTurningOn() {
mKeyguardStatusViewController.dozeTimeTick();
}
@@ -2864,7 +2856,8 @@ public final class NotificationPanelViewController implements Dumpable {
}
}
- public void setPanelAlpha(int alpha, boolean animate) {
+ @Override
+ public void setAlpha(int alpha, boolean animate) {
if (mPanelAlpha != alpha) {
mPanelAlpha = alpha;
PropertyAnimator.setProperty(mView, mPanelAlphaAnimator, alpha, alpha == 255
@@ -2873,17 +2866,18 @@ public final class NotificationPanelViewController implements Dumpable {
}
}
- public void setPanelAlphaEndAction(Runnable r) {
+ @Override
+ public void setAlphaChangeAnimationEndAction(Runnable r) {
mPanelAlphaEndAction = r;
}
- public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+ private void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
mHeadsUpAnimatingAway = headsUpAnimatingAway;
mNotificationStackScrollLayoutController.setHeadsUpAnimatingAway(headsUpAnimatingAway);
updateVisibility();
}
- /** Set whether the bouncer is showing. */
+ @Override
public void setBouncerShowing(boolean bouncerShowing) {
mBouncerShowing = bouncerShowing;
updateVisibility();
@@ -2894,7 +2888,7 @@ public final class NotificationPanelViewController implements Dumpable {
return headsUpVisible || isExpanded() || mBouncerShowing;
}
- public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
+ private void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager;
mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
@@ -2938,6 +2932,7 @@ public final class NotificationPanelViewController implements Dumpable {
}
}
+ @Override
public int getBarState() {
return mBarState;
}
@@ -2987,7 +2982,8 @@ public final class NotificationPanelViewController implements Dumpable {
&& mBarState == StatusBarState.SHADE;
}
- public boolean hideStatusBarIconsWhenExpanded() {
+ @Override
+ public boolean shouldHideStatusBarIconsWhenExpanded() {
if (mIsLaunchAnimationRunning) {
return mHideIconsDuringLaunchAnimation;
}
@@ -2998,6 +2994,7 @@ public final class NotificationPanelViewController implements Dumpable {
return !mShowIconsWhenExpanded;
}
+ @Override
public void setTouchAndAnimationDisabled(boolean disabled) {
mTouchDisabled = disabled;
if (mTouchDisabled) {
@@ -3010,12 +3007,7 @@ public final class NotificationPanelViewController implements Dumpable {
mNotificationStackScrollLayoutController.setAnimationsEnabled(!disabled);
}
- /**
- * Sets the dozing state.
- *
- * @param dozing {@code true} when dozing.
- * @param animate if transition should be animated.
- */
+ @Override
public void setDozing(boolean dozing, boolean animate) {
if (dozing == mDozing) return;
mView.setDozing(dozing);
@@ -3040,6 +3032,7 @@ public final class NotificationPanelViewController implements Dumpable {
updateKeyguardStatusViewAlignment(animate);
}
+ @Override
public void setPulsing(boolean pulsing) {
mPulsing = pulsing;
final boolean
@@ -3058,6 +3051,7 @@ public final class NotificationPanelViewController implements Dumpable {
updateKeyguardStatusViewAlignment(/* animate= */ true);
}
+ @Override
public void setAmbientIndicationTop(int ambientIndicationTop, boolean ambientTextVisible) {
int ambientIndicationBottomPadding = 0;
if (ambientTextVisible) {
@@ -3070,6 +3064,7 @@ public final class NotificationPanelViewController implements Dumpable {
}
}
+ @Override
public void dozeTimeTick() {
mLockIconViewController.dozeTimeTick();
mKeyguardStatusViewController.dozeTimeTick();
@@ -3078,15 +3073,17 @@ public final class NotificationPanelViewController implements Dumpable {
}
}
- public void setStatusAccessibilityImportance(int mode) {
+ void setStatusAccessibilityImportance(int mode) {
mKeyguardStatusViewController.setStatusAccessibilityImportance(mode);
}
//TODO(b/254875405): this should be removed.
+ @Override
public KeyguardBottomAreaView getKeyguardBottomAreaView() {
return mKeyguardBottomArea;
}
+ @Override
public void applyLaunchAnimationProgress(float linearProgress) {
boolean hideIcons = LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS,
linearProgress, ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
@@ -3098,20 +3095,43 @@ public final class NotificationPanelViewController implements Dumpable {
}
}
- public void addTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
- mTrackingHeadsUpListeners.add(listener);
- }
+ private class ShadeHeadsUpTrackerImpl implements ShadeHeadsUpTracker {
+ @Override
+ public void addTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
+ mTrackingHeadsUpListeners.add(listener);
+ }
+
+ @Override
+ public void removeTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
+ mTrackingHeadsUpListeners.remove(listener);
+ }
+
+ @Override
+ public void setHeadsUpAppearanceController(
+ HeadsUpAppearanceController headsUpAppearanceController) {
+ mHeadsUpAppearanceController = headsUpAppearanceController;
+ }
+
+ @Override
+ @Nullable public ExpandableNotificationRow getTrackedHeadsUpNotification() {
+ return mTrackedHeadsUpNotification;
+ }
- public void removeTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
- mTrackingHeadsUpListeners.remove(listener);
+ private void updateTrackingHeadsUp(@Nullable ExpandableNotificationRow pickedChild) {
+ mTrackedHeadsUpNotification = pickedChild;
+ for (int i = 0; i < mTrackingHeadsUpListeners.size(); i++) {
+ Consumer<ExpandableNotificationRow> listener = mTrackingHeadsUpListeners.get(i);
+ listener.accept(pickedChild);
+ }
+ }
}
- public void setHeadsUpAppearanceController(
- HeadsUpAppearanceController headsUpAppearanceController) {
- mHeadsUpAppearanceController = headsUpAppearanceController;
+ @Override
+ public ShadeHeadsUpTracker getShadeHeadsUpTracker() {
+ return mShadeHeadsUpTracker;
}
- /** Called before animating Keyguard dismissal, i.e. the animation dismissing the bouncer. */
+ @Override
public void startBouncerPreHideAnimation() {
if (mKeyguardQsUserSwitchController != null) {
mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
@@ -3129,74 +3149,90 @@ public final class NotificationPanelViewController implements Dumpable {
}
}
- /** Updates the views to the initial state for the fold to AOD animation. */
- public void prepareFoldToAodAnimation() {
- // Force show AOD UI even if we are not locked
- showAodUi();
-
- // Move the content of the AOD all the way to the left
- // so we can animate to the initial position
- final int translationAmount = mView.getResources().getDimensionPixelSize(
- R.dimen.below_clock_padding_start);
- mView.setTranslationX(-translationAmount);
- mView.setAlpha(0);
+ @Override
+ public ShadeFoldAnimator getShadeFoldAnimator() {
+ return mShadeFoldAnimator;
}
- /**
- * Starts fold to AOD animation.
- *
- * @param startAction invoked when the animation starts.
- * @param endAction invoked when the animation finishes, also if it was cancelled.
- * @param cancelAction invoked when the animation is cancelled, before endAction.
- */
- public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
- Runnable cancelAction) {
- final ViewPropertyAnimator viewAnimator = mView.animate();
- viewAnimator.cancel();
- viewAnimator
- .translationX(0)
- .alpha(1f)
- .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
- .setInterpolator(EMPHASIZED_DECELERATE)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- startAction.run();
- }
+ private final class ShadeFoldAnimatorImpl implements ShadeFoldAnimator {
+ /** Updates the views to the initial state for the fold to AOD animation. */
+ @Override
+ public void prepareFoldToAodAnimation() {
+ // Force show AOD UI even if we are not locked
+ showAodUi();
- @Override
- public void onAnimationCancel(Animator animation) {
- cancelAction.run();
- }
+ // Move the content of the AOD all the way to the left
+ // so we can animate to the initial position
+ final int translationAmount = mView.getResources().getDimensionPixelSize(
+ R.dimen.below_clock_padding_start);
+ mView.setTranslationX(-translationAmount);
+ mView.setAlpha(0);
+ }
- @Override
- public void onAnimationEnd(Animator animation) {
- endAction.run();
+ /**
+ * Starts fold to AOD animation.
+ *
+ * @param startAction invoked when the animation starts.
+ * @param endAction invoked when the animation finishes, also if it was cancelled.
+ * @param cancelAction invoked when the animation is cancelled, before endAction.
+ */
+ @Override
+ public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
+ Runnable cancelAction) {
+ final ViewPropertyAnimator viewAnimator = mView.animate();
+ viewAnimator.cancel();
+ viewAnimator
+ .translationX(0)
+ .alpha(1f)
+ .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
+ .setInterpolator(EMPHASIZED_DECELERATE)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ startAction.run();
+ }
- viewAnimator.setListener(null);
- viewAnimator.setUpdateListener(null);
- }
- })
- .setUpdateListener(anim ->
- mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction()))
- .start();
- }
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ cancelAction.run();
+ }
- /** Cancels fold to AOD transition and resets view state. */
- public void cancelFoldToAodAnimation() {
- cancelAnimation();
- resetAlpha();
- resetTranslation();
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ endAction.run();
+
+ viewAnimator.setListener(null);
+ viewAnimator.setUpdateListener(null);
+ }
+ })
+ .setUpdateListener(anim ->
+ mKeyguardStatusViewController.animateFoldToAod(
+ anim.getAnimatedFraction()))
+ .start();
+ }
+
+ /** Cancels fold to AOD transition and resets view state. */
+ @Override
+ public void cancelFoldToAodAnimation() {
+ cancelAnimation();
+ resetAlpha();
+ resetTranslation();
+ }
+
+ /** Returns the NotificationPanelView. */
+ @Override
+ public ViewGroup getView() {
+ // TODO(b/254878364): remove this method, or at least reduce references to it.
+ return mView;
+ }
}
+ @Override
public void setImportantForAccessibility(int mode) {
mView.setImportantForAccessibility(mode);
}
- /**
- * Do not let the user drag the shade up and down for the current touch session.
- * This is necessary to avoid shade expansion while/after the bouncer is dismissed.
- */
+ @Override
public void blockExpansionForCurrentTouch() {
mBlockingExpansionForCurrentTouch = mTracking;
}
@@ -3238,7 +3274,6 @@ public final class NotificationPanelViewController implements Dumpable {
ipw.print("mDisplayRightInset="); ipw.println(mDisplayRightInset);
ipw.print("mDisplayLeftInset="); ipw.println(mDisplayLeftInset);
ipw.print("mIsExpanding="); ipw.println(mIsExpanding);
- ipw.print("mHeaderDebugInfo="); ipw.println(mHeaderDebugInfo);
ipw.print("mHeadsUpStartHeight="); ipw.println(mHeadsUpStartHeight);
ipw.print("mListenForHeadsUp="); ipw.println(mListenForHeadsUp);
ipw.print("mNavigationBarBottomHeight="); ipw.println(mNavigationBarBottomHeight);
@@ -3327,37 +3362,41 @@ public final class NotificationPanelViewController implements Dumpable {
).printTableData(ipw);
}
+ private final class ShadeNotificationPresenterImpl implements ShadeNotificationPresenter{
+ @Override
+ public RemoteInputController.Delegate createRemoteInputDelegate() {
+ return mNotificationStackScrollLayoutController.createDelegate();
+ }
- public RemoteInputController.Delegate createRemoteInputDelegate() {
- return mNotificationStackScrollLayoutController.createDelegate();
- }
-
- public boolean hasPulsingNotifications() {
- return mNotificationListContainer.hasPulsingNotifications();
- }
+ @Override
+ public boolean hasPulsingNotifications() {
+ return mNotificationListContainer.hasPulsingNotifications();
+ }
- public ActivatableNotificationView getActivatedChild() {
- return mNotificationStackScrollLayoutController.getActivatedChild();
- }
+ @Override
+ public ActivatableNotificationView getActivatedChild() {
+ return mNotificationStackScrollLayoutController.getActivatedChild();
+ }
- public void setActivatedChild(ActivatableNotificationView o) {
- mNotificationStackScrollLayoutController.setActivatedChild(o);
+ @Override
+ public void setActivatedChild(ActivatableNotificationView o) {
+ mNotificationStackScrollLayoutController.setActivatedChild(o);
+ }
}
- public void runAfterAnimationFinished(Runnable r) {
- mNotificationStackScrollLayoutController.runAfterAnimationFinished(r);
+ @Override
+ public ShadeNotificationPresenter getShadeNotificationPresenter() {
+ return mShadeNotificationPresenter;
}
- /**
- * Initialize objects instead of injecting to avoid circular dependencies.
- *
- * @param hideExpandedRunnable a runnable to run when we need to hide the expanded panel.
- */
+ @Override
public void initDependencies(
CentralSurfaces centralSurfaces,
GestureRecorder recorder,
Runnable hideExpandedRunnable,
- NotificationShelfController notificationShelfController) {
+ NotificationShelfController notificationShelfController,
+ HeadsUpManagerPhone headsUpManager) {
+ setHeadsUpManager(headsUpManager);
// TODO(b/254859580): this can be injected.
mCentralSurfaces = centralSurfaces;
@@ -3369,14 +3408,17 @@ public final class NotificationPanelViewController implements Dumpable {
updateMaxDisplayedNotifications(true);
}
+ @Override
public void resetTranslation() {
mView.setTranslationX(0f);
}
+ @Override
public void resetAlpha() {
mView.setAlpha(1f);
}
+ @Override
public ViewPropertyAnimator fadeOut(long startDelayMs, long durationMs, Runnable endAction) {
mView.animate().cancel();
return mView.animate().alpha(0).setStartDelay(startDelayMs).setDuration(
@@ -3384,6 +3426,7 @@ public final class NotificationPanelViewController implements Dumpable {
endAction);
}
+ @Override
public void resetViewGroupFade() {
ViewGroupFadeHelper.reset(mView);
}
@@ -3396,14 +3439,11 @@ public final class NotificationPanelViewController implements Dumpable {
mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
}
- public void setHeaderDebugInfo(String text) {
- if (DEBUG_DRAWABLE) mHeaderDebugInfo = text;
- }
-
- public String getHeaderDebugInfo() {
- return mHeaderDebugInfo;
+ String getHeaderDebugInfo() {
+ return "USER " + mHeadsUpManager.getUser();
}
+ @Override
public void onThemeChanged() {
mConfigurationListener.onThemeChanged();
}
@@ -3413,22 +3453,17 @@ public final class NotificationPanelViewController implements Dumpable {
return mTouchHandler;
}
+ @Override
public NotificationStackScrollLayoutController getNotificationStackScrollLayoutController() {
return mNotificationStackScrollLayoutController;
}
- public void disable(int state1, int state2, boolean animated) {
+ @Override
+ public void disableHeader(int state1, int state2, boolean animated) {
mShadeHeaderController.disable(state1, state2, animated);
}
- /**
- * Close the keyguard user switcher if it is open and capable of closing.
- *
- * Has no effect if user switcher isn't supported, if the user switcher is already closed, or
- * if the user switcher uses "simple" mode. The simple user switcher cannot be closed.
- *
- * @return true if the keyguard user switcher was open, and is now closed
- */
+ @Override
public boolean closeUserSwitcherIfOpen() {
if (mKeyguardUserSwitcherController != null) {
return mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(
@@ -3453,7 +3488,7 @@ public final class NotificationPanelViewController implements Dumpable {
);
}
- /** Updates notification panel-specific flags on {@link SysUiState}. */
+ @Override
public void updateSystemUiStateFlags() {
if (SysUiState.DEBUG) {
Log.d(TAG, "Updating panel sysui state flags: fullyExpanded="
@@ -3507,7 +3542,7 @@ public final class NotificationPanelViewController implements Dumpable {
event.offsetLocation(-deltaX, -deltaY);
}
- /** If the latency tracker is enabled, begins tracking expand latency. */
+ @Override
public void startExpandLatencyTracking() {
if (mLatencyTracker.isEnabled()) {
mLatencyTracker.onActionStart(LatencyTracker.ACTION_EXPAND_PANEL);
@@ -3516,7 +3551,7 @@ public final class NotificationPanelViewController implements Dumpable {
}
private void startOpening(MotionEvent event) {
- updatePanelExpansionAndVisibility();
+ updateExpansionAndVisibility();
//TODO: keyguard opens QS a different way; log that too?
// Log the position of the swipe that opened the panel
@@ -3561,7 +3596,7 @@ public final class NotificationPanelViewController implements Dumpable {
}
/** Called when a MotionEvent is about to trigger Shade expansion. */
- public void startExpandMotion(float newX, float newY, boolean startTracking,
+ private void startExpandMotion(float newX, float newY, boolean startTracking,
float expandedHeight) {
if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) {
mQsController.beginJankMonitoring(isFullyCollapsed());
@@ -3576,7 +3611,6 @@ public final class NotificationPanelViewController implements Dumpable {
mInitialTouchFromKeyguard = mKeyguardStateController.isShowing();
if (startTracking) {
mTouchSlopExceeded = true;
- mShadeHeightLogger.logFunctionCall("startExpandMotion");
setExpandedHeight(mInitialOffsetOnTouch);
onTrackingStarted();
}
@@ -3728,7 +3762,6 @@ public final class NotificationPanelViewController implements Dumpable {
@VisibleForTesting
void setExpandedHeight(float height) {
debugLog("setExpandedHeight(%.1f)", height);
- mShadeHeightLogger.logFunctionCall("setExpandedHeight");
setExpandedHeightInternal(height);
}
@@ -3754,13 +3787,10 @@ public final class NotificationPanelViewController implements Dumpable {
return;
}
- mShadeHeightLogger.logFunctionCall("updateExpandedHeightToMaxHeight");
setExpandedHeight(currentMaxPanelHeight);
}
private void setExpandedHeightInternal(float h) {
- mShadeHeightLogger.logSetExpandedHeightInternal(h, mSystemClock.currentTimeMillis());
-
if (isNaN(h)) {
Log.wtf(TAG, "ExpandedHeight set to NaN");
}
@@ -3793,7 +3823,7 @@ public final class NotificationPanelViewController implements Dumpable {
mExpansionDragDownAmountPx = h;
mAmbientState.setExpansionFraction(mExpandedFraction);
onHeightUpdated(mExpandedHeight);
- updatePanelExpansionAndVisibility();
+ updateExpansionAndVisibility();
});
}
@@ -3817,9 +3847,9 @@ public final class NotificationPanelViewController implements Dumpable {
}
/** Sets the expanded height relative to a number from 0 to 1. */
- public void setExpandedFraction(float frac) {
+ @VisibleForTesting
+ void setExpandedFraction(float frac) {
final int maxDist = getMaxPanelTransitionDistance();
- mShadeHeightLogger.logFunctionCall("setExpandedFraction");
setExpandedHeight(maxDist * frac);
}
@@ -3831,27 +3861,13 @@ public final class NotificationPanelViewController implements Dumpable {
return mExpandedFraction;
}
- /**
- * This method should not be used anymore, you should probably use {@link #isShadeFullyOpen()}
- * instead. It was overused as indicating if shade is open or we're on keyguard/AOD.
- * Moving forward we should be explicit about the what state we're checking.
- * @return if panel is covering the screen, which means we're in expanded shade or keyguard/AOD
- *
- * @deprecated depends on the state you check, use {@link #isShadeFullyOpen()},
- * {@link #isOnAod()}, {@link #isOnKeyguard()} instead.
- */
- @Deprecated
+ @Override
public boolean isFullyExpanded() {
return mExpandedHeight >= getMaxPanelTransitionDistance();
}
- /**
- * Returns true if shade is fully opened, that is we're actually in the notification shade
- * with QQS or QS. It's different from {@link #isFullyExpanded()} that it will not report
- * shade as always expanded if we're on keyguard/AOD. It will return true only when user goes
- * from keyguard to shade.
- */
- public boolean isShadeFullyOpen() {
+ @Override
+ public boolean isShadeFullyExpanded() {
if (mBarState == SHADE) {
return isFullyExpanded();
} else if (mBarState == SHADE_LOCKED) {
@@ -3862,10 +3878,12 @@ public final class NotificationPanelViewController implements Dumpable {
}
}
+ @Override
public boolean isFullyCollapsed() {
return mExpandedFraction <= 0.0f;
}
+ @Override
public boolean isCollapsing() {
return mClosing || mIsLaunchAnimationRunning;
}
@@ -3874,22 +3892,21 @@ public final class NotificationPanelViewController implements Dumpable {
return mTracking;
}
- /** Returns whether the shade can be collapsed. */
- public boolean canPanelBeCollapsed() {
+ @Override
+ public boolean canBeCollapsed() {
return !isFullyCollapsed() && !mTracking && !mClosing;
}
/** Collapses the shade instantly without animation. */
- public void instantCollapse() {
+ void instantCollapse() {
abortAnimations();
- mShadeHeightLogger.logFunctionCall("instantCollapse");
setExpandedFraction(0f);
if (mExpanding) {
notifyExpandingFinished();
}
if (mInstantExpanding) {
mInstantExpanding = false;
- updatePanelExpansionAndVisibility();
+ updateExpansionAndVisibility();
}
}
@@ -3898,6 +3915,7 @@ public final class NotificationPanelViewController implements Dumpable {
mView.removeCallbacks(mFlingCollapseRunnable);
}
+ @Override
public boolean isUnlockHintRunning() {
return mHintAnimationRunning;
}
@@ -3956,7 +3974,7 @@ public final class NotificationPanelViewController implements Dumpable {
}
/** Returns whether a shade or QS expansion animation is running */
- public boolean isShadeOrQsHeightAnimationRunning() {
+ private boolean isShadeOrQsHeightAnimationRunning() {
return mHeightAnimator != null && !mHintAnimationRunning && !mIsSpringBackAnimation;
}
@@ -3972,7 +3990,7 @@ public final class NotificationPanelViewController implements Dumpable {
public void onAnimationEnd(Animator animation) {
setAnimator(null);
onAnimationFinished.run();
- updatePanelExpansionAndVisibility();
+ updateExpansionAndVisibility();
}
});
animator.start();
@@ -4004,7 +4022,6 @@ public final class NotificationPanelViewController implements Dumpable {
animator.getAnimatedFraction()));
setOverExpansionInternal(expansion, false /* isFromGesture */);
}
- mShadeHeightLogger.logFunctionCall("height animator update");
setExpandedHeightInternal((float) animation.getAnimatedValue());
});
return animator;
@@ -4015,19 +4032,16 @@ public final class NotificationPanelViewController implements Dumpable {
mView.setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE);
}
- /**
- * Updates the panel expansion and {@link NotificationPanelView} visibility if necessary.
- *
- * TODO(b/200063118): Could public calls to this method be replaced with calls to
- * {@link #updateVisibility()}? That would allow us to make this method private.
- */
- public void updatePanelExpansionAndVisibility() {
+
+ @Override
+ public void updateExpansionAndVisibility() {
mShadeExpansionStateManager.onPanelExpansionChanged(
mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
updateVisibility();
}
+ @Override
public boolean isExpanded() {
return mExpandedFraction > 0f
|| mInstantExpanding
@@ -4049,45 +4063,41 @@ public final class NotificationPanelViewController implements Dumpable {
return mClosing;
}
- /** Collapses the shade with an animation duration in milliseconds. */
+ @Override
public void collapseWithDuration(int animationDuration) {
mFixedDuration = animationDuration;
collapse(false /* delayed */, 1.0f /* speedUpFactor */);
mFixedDuration = NO_FIXED_DURATION;
}
- /** Returns the NotificationPanelView. */
- public ViewGroup getView() {
- // TODO(b/254878364): remove this method, or at least reduce references to it.
- return mView;
- }
-
/** */
- public boolean postToView(Runnable action) {
+ boolean postToView(Runnable action) {
return mView.post(action);
}
/** Sends an external (e.g. Status Bar) intercept touch event to the Shade touch handler. */
- public boolean handleExternalInterceptTouch(MotionEvent event) {
+ boolean handleExternalInterceptTouch(MotionEvent event) {
return mTouchHandler.onInterceptTouchEvent(event);
}
- /** Sends an external (e.g. Status Bar) touch event to the Shade touch handler. */
+ @Override
public boolean handleExternalTouch(MotionEvent event) {
return mTouchHandler.onTouchEvent(event);
}
- /** */
- public void requestLayoutOnView() {
+ @Override
+ public void updateTouchableRegion() {
+ //A layout will ensure that onComputeInternalInsets will be called and after that we can
+ // resize the layout. Make sure that the window stays small for one frame until the
+ // touchableRegion is set.
mView.requestLayout();
+ mNotificationShadeWindowController.setForceWindowCollapsed(true);
+ postToView(() -> {
+ mNotificationShadeWindowController.setForceWindowCollapsed(false);
+ });
}
- /** */
- public void resetViewAlphas() {
- ViewGroupFadeHelper.reset(mView);
- }
-
- /** */
+ @Override
public boolean isViewEnabled() {
return mView.isEnabled();
}
@@ -4115,13 +4125,13 @@ public final class NotificationPanelViewController implements Dumpable {
* shade QS are always expanded
*/
private void closeQsIfPossible() {
- boolean openOrOpening = isShadeFullyOpen() || isExpanding();
+ boolean openOrOpening = isShadeFullyExpanded() || isExpanding();
if (!(mSplitShadeEnabled && openOrOpening)) {
mQsController.closeQs();
}
}
- /** TODO: remove need for this delegate (b/254870148) */
+ @Override
public void setQsScrimEnabled(boolean qsScrimEnabled) {
mQsController.setScrimEnabled(qsScrimEnabled);
}
@@ -4224,7 +4234,7 @@ public final class NotificationPanelViewController implements Dumpable {
== firstRow))) {
requestScrollerTopPaddingUpdate(false /* animate */);
}
- if (getKeyguardShowing()) {
+ if (isKeyguardShowing()) {
updateMaxDisplayedNotifications(true);
}
updateExpandedHeightToMaxHeight();
@@ -4470,7 +4480,7 @@ public final class NotificationPanelViewController implements Dumpable {
@Override
public float getLockscreenShadeDragProgress() {
- return NotificationPanelViewController.this.getLockscreenShadeDragProgress();
+ return mQsController.getLockscreenShadeDragProgress();
}
};
@@ -4480,16 +4490,16 @@ public final class NotificationPanelViewController implements Dumpable {
* to the KEYGUARD state, which is a heavy transition that causes jank as 10+ files react to the
* change.
*/
+ @VisibleForTesting
public void showAodUi() {
setDozing(true /* dozing */, false /* animate */);
mStatusBarStateController.setUpcomingState(KEYGUARD);
mStatusBarStateListener.onStateChanged(KEYGUARD);
mStatusBarStateListener.onDozeAmountChanged(1f, 1f);
- mShadeHeightLogger.logFunctionCall("showAodUi");
setExpandedFraction(1f);
}
- /** Sets the overstretch amount in raw pixels when dragging down. */
+ @Override
public void setOverStretchAmount(float amount) {
float progress = amount / mView.getHeight();
float overStretch = Interpolators.getOvershootInterpolation(progress);
@@ -4581,8 +4591,8 @@ public final class NotificationPanelViewController implements Dumpable {
return insets;
}
- /** Removes any pending runnables that would collapse the panel. */
- public void cancelPendingPanelCollapse() {
+ @Override
+ public void cancelPendingCollapse() {
mView.removeCallbacks(mMaybeHideExpandedRunnable);
}
@@ -4655,7 +4665,7 @@ public final class NotificationPanelViewController implements Dumpable {
private void onStatusBarWindowStateChanged(@StatusBarManager.WindowVisibleState int state) {
if (state != WINDOW_STATE_SHOWING
&& mStatusBarStateController.getState() == StatusBarState.SHADE) {
- collapsePanel(
+ collapse(
false /* animate */,
false /* delayed */,
1.0f /* speedUpFactor */);
@@ -4667,6 +4677,7 @@ public final class NotificationPanelViewController implements Dumpable {
private long mLastTouchDownTime = -1L;
/** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
+ @Override
public boolean onInterceptTouchEvent(MotionEvent event) {
mShadeLog.logMotionEvent(event, "NPVC onInterceptTouchEvent");
if (mQsController.disallowTouches()) {
@@ -4928,7 +4939,7 @@ public final class NotificationPanelViewController implements Dumpable {
// On expanding, single mouse click expands the panel instead of dragging.
if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
if (event.getAction() == MotionEvent.ACTION_UP) {
- expand(true);
+ expand(true /* animate */);
}
return true;
}
@@ -5058,7 +5069,6 @@ public final class NotificationPanelViewController implements Dumpable {
// otherwise {@link NotificationStackScrollLayout}
// wrongly enables stack height updates at the start of lockscreen swipe-up
mAmbientState.setSwipingUp(h <= 0);
- mShadeHeightLogger.logFunctionCall("ACTION_MOVE");
setExpandedHeightInternal(newHeight);
}
break;
@@ -5156,7 +5166,7 @@ public final class NotificationPanelViewController implements Dumpable {
@Override
public void setTrackedHeadsUp(ExpandableNotificationRow pickedChild) {
if (pickedChild != null) {
- updateTrackingHeadsUp(pickedChild);
+ mShadeHeadsUpTracker.updateTrackingHeadsUp(pickedChild);
mExpandingFromHeadsUp = true;
}
// otherwise we update the state when the expansion is finished
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 0318fa570a78..2f4cc1467517 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -283,11 +283,15 @@ public class NotificationShadeWindowViewController {
}
mLockIconViewController.onTouchEvent(
ev,
- () -> mService.wakeUpIfDozing(
- mClock.uptimeMillis(),
- mView,
- "LOCK_ICON_TOUCH",
- PowerManager.WAKE_REASON_GESTURE)
+ /* onGestureDetectedRunnable */
+ () -> {
+ mService.userActivity();
+ mService.wakeUpIfDozing(
+ mClock.uptimeMillis(),
+ mView,
+ "LOCK_ICON_TOUCH",
+ PowerManager.WAKE_REASON_GESTURE);
+ }
);
// In case we start outside of the view bounds (below the status bar), we need to
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 07c8e52e7a6c..b31ec3319781 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -766,7 +766,7 @@ public class QuickSettingsController {
/** TODO(b/269742565) Remove this logging */
private void checkCorrectSplitShadeState(float height) {
if (mSplitShadeEnabled && height == 0
- && mPanelViewControllerLazy.get().isShadeFullyOpen()) {
+ && mPanelViewControllerLazy.get().isShadeFullyExpanded()) {
Log.wtfStack(TAG, "qsExpansion set to 0 while split shade is expanding or open");
}
}
@@ -989,13 +989,22 @@ public class QuickSettingsController {
// TODO (b/265193930): remove dependency on NPVC
float shadeExpandedFraction = mBarState == KEYGUARD
- ? mPanelViewControllerLazy.get().getLockscreenShadeDragProgress()
+ ? getLockscreenShadeDragProgress()
: mShadeExpandedFraction;
mShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
mShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
mShadeHeaderController.setQsVisible(mVisible);
}
+ float getLockscreenShadeDragProgress() {
+ // mTransitioningToFullShadeProgress > 0 means we're doing regular lockscreen to shade
+ // transition. If that's not the case we should follow QS expansion fraction for when
+ // user is pulling from the same top to go directly to expanded QS
+ return getTransitioningToFullShadeProgress() > 0
+ ? mLockscreenShadeTransitionController.getQSDragProgress()
+ : computeExpansionFraction();
+ }
+
/** */
public void updateExpansionEnabledAmbient() {
final float scrollRangeToTop = mAmbientState.getTopPadding() - mQuickQsHeaderHeight;
@@ -1309,8 +1318,9 @@ public class QuickSettingsController {
logNotificationsTopPadding("keyguard", topPadding);
return topPadding;
} else {
- topPadding = mQsFrameTranslateController.getNotificationsTopPadding(
- mExpansionHeight, mNotificationStackScrollLayoutController);
+ topPadding = Math.max(mQsFrameTranslateController.getNotificationsTopPadding(
+ mExpansionHeight, mNotificationStackScrollLayoutController),
+ mQuickQsHeaderHeight);
logNotificationsTopPadding("default case", topPadding);
return topPadding;
}
@@ -1521,7 +1531,7 @@ public class QuickSettingsController {
if (scrollY > 0 && !mFullyExpanded) {
// TODO (b/265193930): remove dependency on NPVC
// If we are scrolling QS, we should be fully expanded.
- mPanelViewControllerLazy.get().expandWithQs();
+ mPanelViewControllerLazy.get().expandToQs();
}
}
@@ -1726,7 +1736,7 @@ public class QuickSettingsController {
return true;
}
// TODO (b/265193930): remove dependency on NPVC
- if (mPanelViewControllerLazy.get().getKeyguardShowing()
+ if (mPanelViewControllerLazy.get().isKeyguardShowing()
&& shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
// Dragging down on the lockscreen statusbar should prohibit other interactions
// immediately, otherwise we'll wait on the touchslop. This is to allow
@@ -1790,7 +1800,7 @@ public class QuickSettingsController {
return true;
} else {
mShadeLog.logQsTrackingNotStarted(mInitialTouchY, y, h, touchSlop,
- getExpanded(), mPanelViewControllerLazy.get().getKeyguardShowing(),
+ getExpanded(), mPanelViewControllerLazy.get().isKeyguardShowing(),
isExpansionEnabled());
}
break;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index c1369935db54..826b3ee7a92d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -35,12 +35,12 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
+import dagger.Lazy;
+
import java.util.ArrayList;
import javax.inject.Inject;
-import dagger.Lazy;
-
/** An implementation of {@link ShadeController}. */
@SysUISingleton
public final class ShadeControllerImpl implements ShadeController {
@@ -132,13 +132,13 @@ public final class ShadeControllerImpl implements ShadeController {
"animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags);
}
if (getNotificationShadeWindowView() != null
- && mNotificationPanelViewController.canPanelBeCollapsed()
+ && mNotificationPanelViewController.canBeCollapsed()
&& (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) {
// release focus immediately to kick off focus change transition
mNotificationShadeWindowController.setNotificationShadeFocusable(false);
mNotificationShadeWindowViewController.cancelExpandHelper();
- mNotificationPanelViewController.collapsePanel(true, delayed, speedUpFactor);
+ mNotificationPanelViewController.collapse(true, delayed, speedUpFactor);
}
}
@@ -155,7 +155,7 @@ public final class ShadeControllerImpl implements ShadeController {
@Override
public boolean isShadeFullyOpen() {
- return mNotificationPanelViewController.isShadeFullyOpen();
+ return mNotificationPanelViewController.isShadeFullyExpanded();
}
@Override
@@ -268,7 +268,7 @@ public final class ShadeControllerImpl implements ShadeController {
}
// Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
- mNotificationPanelViewController.collapsePanel(false, false, 1.0f);
+ mNotificationPanelViewController.collapse(false, false, 1.0f);
mExpandedVisible = false;
notifyVisibilityChanged(false);
@@ -290,7 +290,7 @@ public final class ShadeControllerImpl implements ShadeController {
notifyExpandedVisibleChanged(false);
mCommandQueue.recomputeDisableFlags(
mDisplayId,
- mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */);
+ mNotificationPanelViewController.shouldHideStatusBarIconsWhenExpanded());
// Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
// the bouncer appear animation.
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeightLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeightLogger.kt
deleted file mode 100644
index e610b985aef9..000000000000
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeightLogger.kt
+++ /dev/null
@@ -1,52 +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.systemui.shade
-
-import com.android.systemui.log.dagger.ShadeHeightLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import java.text.SimpleDateFormat
-import javax.inject.Inject
-
-private const val TAG = "ShadeHeightLogger"
-
-/**
- * Log the call stack for [NotificationPanelViewController] setExpandedHeightInternal.
- *
- * Tracking bug: b/261593829
- */
-class ShadeHeightLogger
-@Inject constructor(
- @ShadeHeightLog private val buffer: LogBuffer,
-) {
-
- private val dateFormat = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS")
-
- fun logFunctionCall(functionName: String) {
- buffer.log(TAG, DEBUG, {
- str1 = functionName
- }, {
- "$str1"
- })
- }
-
- fun logSetExpandedHeightInternal(h: Float, time: Long) {
- buffer.log(TAG, DEBUG, {
- double1 = h.toDouble()
- long1 = time
- }, {
- "setExpandedHeightInternal=$double1 time=${dateFormat.format(long1)}"
- })
- }
-} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
new file mode 100644
index 000000000000..b698bd3e6468
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shade
+
+import android.view.ViewPropertyAnimator
+import com.android.systemui.statusbar.GestureRecorder
+import com.android.systemui.statusbar.NotificationShelfController
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
+
+/**
+ * Allows CentralSurfacesImpl to interact with the shade. Only CentralSurfacesImpl should reference
+ * this class. If any method in this class is needed outside of CentralSurfacesImpl, it must be
+ * pulled up into ShadeViewController.
+ */
+interface ShadeSurface : ShadeViewController {
+ /** Initialize objects instead of injecting to avoid circular dependencies. */
+ fun initDependencies(
+ centralSurfaces: CentralSurfaces,
+ recorder: GestureRecorder,
+ hideExpandedRunnable: Runnable,
+ notificationShelfController: NotificationShelfController,
+ headsUpManager: HeadsUpManagerPhone
+ )
+
+ /**
+ * Animate QS collapse by flinging it. If QS is expanded, it will collapse into QQS and stop. If
+ * in split shade, it will collapse the whole shade.
+ *
+ * @param fullyCollapse Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
+ */
+ fun animateCollapseQs(fullyCollapse: Boolean)
+
+ /** Returns whether the shade can be collapsed. */
+ fun canBeCollapsed(): Boolean
+
+ /** Cancels any pending collapses. */
+ fun cancelPendingCollapse()
+
+ /** Cancels the views current animation. */
+ fun cancelAnimation()
+
+ /**
+ * Close the keyguard user switcher if it is open and capable of closing.
+ *
+ * Has no effect if user switcher isn't supported, if the user switcher is already closed, or if
+ * the user switcher uses "simple" mode. The simple user switcher cannot be closed.
+ *
+ * @return true if the keyguard user switcher was open, and is now closed
+ */
+ fun closeUserSwitcherIfOpen(): Boolean
+
+ /** Input focus transfer is about to happen. */
+ fun startWaitingForExpandGesture()
+
+ /**
+ * Called when this view is no longer waiting for input focus transfer.
+ *
+ * There are two scenarios behind this function call. First, input focus transfer has
+ * successfully happened and this view already received synthetic DOWN event.
+ * (mExpectingSynthesizedDown == false). Do nothing.
+ *
+ * Second, before input focus transfer finished, user may have lifted finger in previous window
+ * and this window never received synthetic DOWN event. (mExpectingSynthesizedDown == true). In
+ * this case, we use the velocity to trigger fling event.
+ *
+ * @param velocity unit is in px / millis
+ */
+ fun stopWaitingForExpandGesture(cancel: Boolean, velocity: Float)
+
+ /** Animates the view from its current alpha to zero then runs the runnable. */
+ fun fadeOut(startDelayMs: Long, durationMs: Long, endAction: Runnable): ViewPropertyAnimator
+
+ /** Set whether the bouncer is showing. */
+ fun setBouncerShowing(bouncerShowing: Boolean)
+
+ /**
+ * Sets whether the shade can handle touches and/or animate, canceling any touch handling or
+ * animations in progress.
+ */
+ fun setTouchAndAnimationDisabled(disabled: Boolean)
+
+ /**
+ * Sets the dozing state.
+ *
+ * @param dozing `true` when dozing.
+ * @param animate if transition should be animated.
+ */
+ fun setDozing(dozing: Boolean, animate: Boolean)
+
+ /** @see view.setImportantForAccessibility */
+ fun setImportantForAccessibility(mode: Int)
+
+ /** Sets Qs ScrimEnabled and updates QS state. */
+ fun setQsScrimEnabled(qsScrimEnabled: Boolean)
+
+ /** Sets the view's X translation to zero. */
+ fun resetTranslation()
+
+ /** Sets the view's alpha to max. */
+ fun resetAlpha()
+
+ /** @see ViewGroupFadeHelper.reset */
+ fun resetViewGroupFade()
+
+ /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
+ fun onBackPressed()
+
+ /** Sets progress of the predictive back animation. */
+ fun onBackProgressed(progressFraction: Float)
+
+ /** @see com.android.systemui.keyguard.ScreenLifecycle.Observer.onScreenTurningOn */
+ fun onScreenTurningOn()
+
+ /**
+ * Called when the device's theme changes.
+ *
+ * TODO(b/274655539) delete?
+ */
+ fun onThemeChanged()
+
+ /** Updates the shade expansion and [NotificationPanelView] visibility if necessary. */
+ fun updateExpansionAndVisibility()
+
+ /** Updates all field values drawn from Resources. */
+ fun updateResources()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
new file mode 100644
index 000000000000..34c9f6d16fff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shade
+
+import android.view.MotionEvent
+import android.view.ViewGroup
+import com.android.systemui.statusbar.RemoteInputController
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
+import java.util.function.Consumer
+
+/**
+ * Controller for the top level shade view
+ *
+ * @see NotificationPanelViewController
+ */
+interface ShadeViewController {
+ /** Expand the shade either animated or instantly. */
+ fun expand(animate: Boolean)
+
+ /** Animates to an expanded shade with QS expanded. If the shade starts expanded, expands QS. */
+ fun expandToQs()
+
+ /**
+ * Expand shade so that notifications are visible. Non-split shade: just expanding shade or
+ * collapsing QS when they're expanded. Split shade: only expanding shade, notifications are
+ * always visible
+ *
+ * Called when `adb shell cmd statusbar expand-notifications` is executed.
+ */
+ fun expandToNotifications()
+
+ /** Returns whether the shade is expanding or collapsing itself or quick settings. */
+ val isExpanding: Boolean
+
+ /**
+ * Returns whether the shade height is greater than zero (i.e. partially or fully expanded),
+ * there is a HUN, the shade is animating, or the shade is instantly expanding.
+ */
+ val isExpanded: Boolean
+
+ /**
+ * Returns whether the shade height is greater than zero or the shade is expecting a synthesized
+ * down event.
+ */
+ @get:Deprecated("use {@link #isExpanded()} instead") val isPanelExpanded: Boolean
+
+ /** Returns whether the shade is fully expanded in either QS or QQS. */
+ val isShadeFullyExpanded: Boolean
+
+ /**
+ * Animates the collapse of a shade with the given delay and the default duration divided by
+ * speedUpFactor.
+ */
+ fun collapse(delayed: Boolean, speedUpFactor: Float)
+
+ /** Collapses the shade. */
+ fun collapse(animate: Boolean, delayed: Boolean, speedUpFactor: Float)
+
+ /** Collapses the shade with an animation duration in milliseconds. */
+ fun collapseWithDuration(animationDuration: Int)
+
+ /** Returns whether the shade is in the process of collapsing. */
+ val isCollapsing: Boolean
+
+ /** Returns whether shade's height is zero. */
+ val isFullyCollapsed: Boolean
+
+ /** Returns whether the shade is tracking touches for expand/collapse of the shade or QS. */
+ val isTracking: Boolean
+
+ /** Returns whether the shade's top level view is enabled. */
+ val isViewEnabled: Boolean
+
+ /** Returns whether status bar icons should be hidden when the shade is expanded. */
+ fun shouldHideStatusBarIconsWhenExpanded(): Boolean
+
+ /**
+ * Do not let the user drag the shade up and down for the current touch session. This is
+ * necessary to avoid shade expansion while/after the bouncer is dismissed.
+ */
+ fun blockExpansionForCurrentTouch()
+
+ /**
+ * Disables the shade header.
+ *
+ * @see ShadeHeaderController.disable
+ */
+ fun disableHeader(state1: Int, state2: Int, animated: Boolean)
+
+ /** If the latency tracker is enabled, begins tracking expand latency. */
+ fun startExpandLatencyTracking()
+
+ /** Called before animating Keyguard dismissal, i.e. the animation dismissing the bouncer. */
+ fun startBouncerPreHideAnimation()
+
+ /** Called once every minute while dozing. */
+ fun dozeTimeTick()
+
+ /** Close guts, notification menus, and QS. Set scroll and overscroll to 0. */
+ fun resetViews(animate: Boolean)
+
+ /** Returns the StatusBarState. */
+ val barState: Int
+
+ /**
+ * Returns the bottom part of the keyguard, which contains quick affordances.
+ *
+ * TODO(b/275550429): this should be removed.
+ */
+ val keyguardBottomAreaView: KeyguardBottomAreaView?
+
+ /** Returns the NSSL controller. */
+ val notificationStackScrollLayoutController: NotificationStackScrollLayoutController
+
+ /** Sets the amount of progress in the status bar launch animation. */
+ fun applyLaunchAnimationProgress(linearProgress: Float)
+
+ /** Sets whether the status bar launch animation is currently running. */
+ fun setIsLaunchAnimationRunning(running: Boolean)
+
+ /** Sets the alpha value of the shade to a value between 0 and 255. */
+ fun setAlpha(alpha: Int, animate: Boolean)
+
+ /**
+ * Sets the runnable to run after the alpha change animation completes.
+ *
+ * @see .setAlpha
+ */
+ fun setAlphaChangeAnimationEndAction(r: Runnable)
+
+ /** Sets whether the screen has temporarily woken up to display notifications. */
+ fun setPulsing(pulsing: Boolean)
+
+ /** Sets the top spacing for the ambient indicator. */
+ fun setAmbientIndicationTop(ambientIndicationTop: Int, ambientTextVisible: Boolean)
+
+ /** Updates notification panel-specific flags on [SysUiState]. */
+ fun updateSystemUiStateFlags()
+
+ /** Ensures that the touchable region is updated. */
+ fun updateTouchableRegion()
+
+ // ******* Begin Keyguard Section *********
+ /** Animate to expanded shade after a delay in ms. Used for lockscreen to shade transition. */
+ fun transitionToExpandedShade(delay: Long)
+
+ /**
+ * Returns whether the unlock hint animation is running. The unlock hint animation is when the
+ * user taps the lock screen, causing the contents of the lock screen visually bounce.
+ */
+ val isUnlockHintRunning: Boolean
+
+ /**
+ * Set the alpha and translationY of the keyguard elements which only show on the lockscreen,
+ * but not in shade locked / shade. This is used when dragging down to the full shade.
+ */
+ fun setKeyguardTransitionProgress(keyguardAlpha: Float, keyguardTranslationY: Int)
+
+ /** Sets the overstretch amount in raw pixels when dragging down. */
+ fun setOverStretchAmount(amount: Float)
+
+ /**
+ * Sets the alpha value to be set on the keyguard status bar.
+ *
+ * @param alpha value between 0 and 1. -1 if the value is to be reset.
+ */
+ fun setKeyguardStatusBarAlpha(alpha: Float)
+
+ /**
+ * This method should not be used anymore, you should probably use [.isShadeFullyOpen] instead.
+ * It was overused as indicating if shade is open or we're on keyguard/AOD. Moving forward we
+ * should be explicit about the what state we're checking.
+ *
+ * @return if panel is covering the screen, which means we're in expanded shade or keyguard/AOD
+ */
+ @Deprecated(
+ "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
+ "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
+ )
+ fun isFullyExpanded(): Boolean
+
+ /** Sends an external (e.g. Status Bar) touch event to the Shade touch handler. */
+ fun handleExternalTouch(event: MotionEvent): Boolean
+
+ // ******* End Keyguard Section *********
+
+ /** Returns the ShadeHeadsUpTracker. */
+ val shadeHeadsUpTracker: ShadeHeadsUpTracker
+
+ /** Returns the ShadeFoldAnimator. */
+ val shadeFoldAnimator: ShadeFoldAnimator
+
+ /** Returns the ShadeNotificationPresenter. */
+ val shadeNotificationPresenter: ShadeNotificationPresenter
+}
+
+/** Manages listeners for when users begin expanding the shade from a HUN. */
+interface ShadeHeadsUpTracker {
+ /** Add a listener for when the user starts expanding the shade from a HUN. */
+ fun addTrackingHeadsUpListener(listener: Consumer<ExpandableNotificationRow>)
+
+ /** Remove a listener for when the user starts expanding the shade from a HUN. */
+ fun removeTrackingHeadsUpListener(listener: Consumer<ExpandableNotificationRow>)
+
+ /** Set the controller for the appearance of HUNs in the icon area and the header itself. */
+ fun setHeadsUpAppearanceController(headsUpAppearanceController: HeadsUpAppearanceController?)
+
+ /** The notification row that was touched to initiate shade expansion. */
+ val trackedHeadsUpNotification: ExpandableNotificationRow?
+}
+
+/** Handles the lifecycle of the shade's animation that happens when folding a foldable. */
+interface ShadeFoldAnimator {
+ /** Updates the views to the initial state for the fold to AOD animation. */
+ fun prepareFoldToAodAnimation()
+
+ /**
+ * Starts fold to AOD animation.
+ *
+ * @param startAction invoked when the animation starts.
+ * @param endAction invoked when the animation finishes, also if it was cancelled.
+ * @param cancelAction invoked when the animation is cancelled, before endAction.
+ */
+ fun startFoldToAodAnimation(startAction: Runnable, endAction: Runnable, cancelAction: Runnable)
+
+ /** Cancels fold to AOD transition and resets view state. */
+ fun cancelFoldToAodAnimation()
+
+ /** Returns the main view of the shade. */
+ val view: ViewGroup
+}
+
+/** Handles the shade's interactions with StatusBarNotificationPresenter. */
+interface ShadeNotificationPresenter {
+ /** Returns a new delegate for some view controller pieces of the remote input process. */
+ fun createRemoteInputDelegate(): RemoteInputController.Delegate
+
+ /** Returns whether the screen has temporarily woken up to display notifications. */
+ fun hasPulsingNotifications(): Boolean
+
+ /** The current activated notification. */
+ var activatedChild: ActivatableNotificationView?
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
index fbb51aef06f0..07b686948d7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar
import android.app.ActivityManager
import android.content.res.Resources
import android.os.SystemProperties
+import android.os.Trace
import android.util.IndentingPrintWriter
import android.util.MathUtils
import android.view.CrossWindowBlurListeners
@@ -42,7 +43,7 @@ open class BlurUtils @Inject constructor(
) : Dumpable {
val minBlurRadius = resources.getDimensionPixelSize(R.dimen.min_window_blur_radius)
val maxBlurRadius = resources.getDimensionPixelSize(R.dimen.max_window_blur_radius)
-
+ private val traceCookie = System.identityHashCode(this)
private var lastAppliedBlur = 0
init {
@@ -85,10 +86,13 @@ open class BlurUtils @Inject constructor(
if (supportsBlursOnWindows()) {
it.setBackgroundBlurRadius(viewRootImpl.surfaceControl, radius)
if (lastAppliedBlur == 0 && radius != 0) {
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, TRACK_NAME,
+ EARLY_WAKEUP_SLICE_NAME, traceCookie)
it.setEarlyWakeupStart()
}
if (lastAppliedBlur != 0 && radius == 0) {
it.setEarlyWakeupEnd()
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TRACK_NAME, traceCookie)
}
lastAppliedBlur = radius
}
@@ -125,4 +129,9 @@ open class BlurUtils @Inject constructor(
it.println("isHighEndGfx: ${ActivityManager.isHighEndGfx()}")
}
}
+
+ companion object {
+ const val TRACK_NAME = "BlurUtils"
+ const val EARLY_WAKEUP_SLICE_NAME = "eEarlyWakeup"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 9a9503c8cd9c..63e29d105cd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -320,7 +320,7 @@ class LockscreenShadeTransitionController @Inject constructor(
startingChild.onExpandedByGesture(
true /* drag down is always an open */)
}
- notificationPanelController.animateToFullShade(delay)
+ notificationPanelController.transitionToExpandedShade(delay)
callbacks.forEach { it.setTransitionToFullShadeAmount(0f,
true /* animated */, delay) }
@@ -531,7 +531,7 @@ class LockscreenShadeTransitionController @Inject constructor(
} else {
// Let's only animate notifications
animationHandler = { delay: Long ->
- notificationPanelController.animateToFullShade(delay)
+ notificationPanelController.transitionToExpandedShade(delay)
}
}
goToLockedShadeInternal(expandedView, animationHandler,
@@ -649,7 +649,7 @@ class LockscreenShadeTransitionController @Inject constructor(
*/
private fun performDefaultGoToFullShadeAnimation(delay: Long) {
logger.logDefaultGoToFullShadeAnimation(delay)
- notificationPanelController.animateToFullShade(delay)
+ notificationPanelController.transitionToExpandedShade(delay)
animateAppear(delay)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 8874f59d6c17..20af6cadeed5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -16,14 +16,17 @@
package com.android.systemui.statusbar.notification
-import android.animation.ObjectAnimator
import android.util.FloatProperty
+import android.view.animation.Interpolator
import androidx.annotation.VisibleForTesting
+import androidx.core.animation.ObjectAnimator
import com.android.systemui.Dumpable
import com.android.systemui.animation.Interpolators
+import com.android.systemui.animation.InterpolatorsAndroidX
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.NotificationPanelViewController.WAKEUP_ANIMATION_DELAY_MS
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.shade.ShadeExpansionListener
import com.android.systemui.statusbar.StatusBarState
@@ -36,12 +39,17 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController.OnBypassSta
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import com.android.systemui.util.doOnEnd
+import com.android.systemui.util.doOnStart
import java.io.PrintWriter
import javax.inject.Inject
+import kotlin.math.max
import kotlin.math.min
@SysUISingleton
-class NotificationWakeUpCoordinator @Inject constructor(
+class NotificationWakeUpCoordinator
+@Inject
+constructor(
dumpManager: DumpManager,
private val mHeadsUpManager: HeadsUpManager,
private val statusBarStateController: StatusBarStateController,
@@ -49,27 +57,25 @@ class NotificationWakeUpCoordinator @Inject constructor(
private val dozeParameters: DozeParameters,
private val screenOffAnimationController: ScreenOffAnimationController,
private val logger: NotificationWakeUpCoordinatorLogger,
-) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, ShadeExpansionListener,
+) :
+ OnHeadsUpChangedListener,
+ StatusBarStateController.StateListener,
+ ShadeExpansionListener,
Dumpable {
-
- private val mNotificationVisibility = object : FloatProperty<NotificationWakeUpCoordinator>(
- "notificationVisibility") {
-
- override fun setValue(coordinator: NotificationWakeUpCoordinator, value: Float) {
- coordinator.setVisibilityAmount(value)
- }
-
- override fun get(coordinator: NotificationWakeUpCoordinator): Float? {
- return coordinator.mLinearVisibilityAmount
- }
- }
private lateinit var mStackScrollerController: NotificationStackScrollLayoutController
private var mVisibilityInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE
- private var mLinearDozeAmount: Float = 0.0f
- private var mDozeAmount: Float = 0.0f
- private var mDozeAmountSource: String = "init"
- private var mNotifsHiddenByDozeAmountOverride: Boolean = false
+ private var inputLinearDozeAmount: Float = 0.0f
+ private var inputEasedDozeAmount: Float = 0.0f
+ private var delayedDozeAmountOverride: Float = 0.0f
+ private var delayedDozeAmountAnimator: ObjectAnimator? = null
+ /** Valid values: {1f, 0f, null} null => use input */
+ private var hardDozeAmountOverride: Float? = null
+ private var hardDozeAmountOverrideSource: String = "n/a"
+ private var outputLinearDozeAmount: Float = 0.0f
+ private var outputEasedDozeAmount: Float = 0.0f
+ @VisibleForTesting val dozeAmountInterpolator: Interpolator = Interpolators.FAST_OUT_SLOW_IN
+
private var mNotificationVisibleAmount = 0.0f
private var mNotificationsVisible = false
private var mNotificationsVisibleForExpansion = false
@@ -84,27 +90,32 @@ class NotificationWakeUpCoordinator @Inject constructor(
var fullyAwake: Boolean = false
var wakingUp = false
- set(value) {
+ private set(value) {
field = value
willWakeUp = false
if (value) {
- if (mNotificationsVisible && !mNotificationsVisibleForExpansion &&
- !bypassController.bypassEnabled) {
+ if (
+ mNotificationsVisible &&
+ !mNotificationsVisibleForExpansion &&
+ !bypassController.bypassEnabled
+ ) {
// We're waking up while pulsing, let's make sure the animation looks nice
mStackScrollerController.wakeUpFromPulse()
}
if (bypassController.bypassEnabled && !mNotificationsVisible) {
// Let's make sure our huns become visible once we are waking up in case
// they were blocked by the proximity sensor
- updateNotificationVisibility(animate = shouldAnimateVisibility(),
- increaseSpeed = false)
+ updateNotificationVisibility(
+ animate = shouldAnimateVisibility(),
+ increaseSpeed = false
+ )
}
}
}
var willWakeUp = false
set(value) {
- if (!value || mDozeAmount != 0.0f) {
+ if (!value || outputLinearDozeAmount != 0.0f) {
field = value
}
}
@@ -118,8 +129,10 @@ class NotificationWakeUpCoordinator @Inject constructor(
// Only when setting pulsing to true we want an immediate update, since we get
// this already when the doze service finishes which is usually before we get
// the waking up callback
- updateNotificationVisibility(animate = shouldAnimateVisibility(),
- increaseSpeed = false)
+ updateNotificationVisibility(
+ animate = shouldAnimateVisibility(),
+ increaseSpeed = false
+ )
}
}
@@ -133,17 +146,17 @@ class NotificationWakeUpCoordinator @Inject constructor(
}
}
- /**
- * True if we can show pulsing heads up notifications
- */
+ /** True if we can show pulsing heads up notifications */
var canShowPulsingHuns: Boolean = false
private set
get() {
var canShow = pulsing
if (bypassController.bypassEnabled) {
// We also allow pulsing on the lock screen!
- canShow = canShow || (wakingUp || willWakeUp || fullyAwake) &&
- statusBarStateController.state == StatusBarState.KEYGUARD
+ canShow =
+ canShow ||
+ (wakingUp || willWakeUp || fullyAwake) &&
+ statusBarStateController.state == StatusBarState.KEYGUARD
// We want to hide the notifications when collapsed too much
if (collapsedEnoughToHide) {
canShow = false
@@ -152,30 +165,38 @@ class NotificationWakeUpCoordinator @Inject constructor(
return canShow
}
- private val bypassStateChangedListener = object : OnBypassStateChangedListener {
- override fun onBypassStateChanged(isEnabled: Boolean) {
- // When the bypass state changes, we have to check whether we should re-show the
- // notifications by clearing the doze amount override which hides them.
- maybeClearDozeAmountOverrideHidingNotifs()
+ private val bypassStateChangedListener =
+ object : OnBypassStateChangedListener {
+ override fun onBypassStateChanged(isEnabled: Boolean) {
+ // When the bypass state changes, we have to check whether we should re-show the
+ // notifications by clearing the doze amount override which hides them.
+ maybeClearHardDozeAmountOverrideHidingNotifs()
+ }
}
- }
init {
dumpManager.registerDumpable(this)
mHeadsUpManager.addListener(this)
statusBarStateController.addCallback(this)
bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener)
- addListener(object : WakeUpListener {
- override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
- if (isFullyHidden && mNotificationsVisibleForExpansion) {
- // When the notification becomes fully invisible, let's make sure our expansion
- // flag also changes. This can happen if the bouncer shows when dragging down
- // and then the screen turning off, where we don't reset this state.
- setNotificationsVisibleForExpansion(visible = false, animate = false,
- increaseSpeed = false)
+ addListener(
+ object : WakeUpListener {
+ override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
+ if (isFullyHidden && mNotificationsVisibleForExpansion) {
+ // When the notification becomes fully invisible, let's make sure our
+ // expansion
+ // flag also changes. This can happen if the bouncer shows when dragging
+ // down
+ // and then the screen turning off, where we don't reset this state.
+ setNotificationsVisibleForExpansion(
+ visible = false,
+ animate = false,
+ increaseSpeed = false
+ )
+ }
}
}
- })
+ )
}
fun setStackScroller(stackScrollerController: NotificationStackScrollLayoutController) {
@@ -221,15 +242,17 @@ class NotificationWakeUpCoordinator @Inject constructor(
wakeUpListeners.remove(listener)
}
- private fun updateNotificationVisibility(
- animate: Boolean,
- increaseSpeed: Boolean
- ) {
+ private fun updateNotificationVisibility(animate: Boolean, increaseSpeed: Boolean) {
// TODO: handle Lockscreen wakeup for bypass when we're not pulsing anymore
var visible = mNotificationsVisibleForExpansion || mHeadsUpManager.hasNotifications()
visible = visible && canShowPulsingHuns
- if (!visible && mNotificationsVisible && (wakingUp || willWakeUp) && mDozeAmount != 0.0f) {
+ if (
+ !visible &&
+ mNotificationsVisible &&
+ (wakingUp || willWakeUp) &&
+ outputLinearDozeAmount != 0.0f
+ ) {
// let's not make notifications invisible while waking up, otherwise the animation
// is strange
return
@@ -257,7 +280,9 @@ class NotificationWakeUpCoordinator @Inject constructor(
override fun onDozeAmountChanged(linear: Float, eased: Float) {
logger.logOnDozeAmountChanged(linear = linear, eased = eased)
- if (overrideDozeAmountIfAnimatingScreenOff(linear)) {
+ inputLinearDozeAmount = linear
+ inputEasedDozeAmount = eased
+ if (overrideDozeAmountIfAnimatingScreenOff()) {
return
}
@@ -265,35 +290,111 @@ class NotificationWakeUpCoordinator @Inject constructor(
return
}
- if (linear != 1.0f && linear != 0.0f &&
- (mLinearDozeAmount == 0.0f || mLinearDozeAmount == 1.0f)) {
- // Let's notify the scroller that an animation started
- notifyAnimationStart(mLinearDozeAmount == 1.0f)
+ if (clearHardDozeAmountOverride()) {
+ return
}
- setDozeAmount(linear, eased, source = "StatusBar")
+
+ updateDozeAmount()
}
- fun setDozeAmount(
- linear: Float,
- eased: Float,
- source: String,
- hidesNotifsByOverride: Boolean = false
- ) {
- val changed = linear != mLinearDozeAmount
- logger.logSetDozeAmount(linear, eased, source, statusBarStateController.state, changed)
- mLinearDozeAmount = linear
- mDozeAmount = eased
- mDozeAmountSource = source
- mNotifsHiddenByDozeAmountOverride = hidesNotifsByOverride
- mStackScrollerController.setDozeAmount(mDozeAmount)
+ private fun setHardDozeAmountOverride(dozing: Boolean, source: String) {
+ logger.logSetDozeAmountOverride(dozing = dozing, source = source)
+ hardDozeAmountOverride = if (dozing) 1f else 0f
+ hardDozeAmountOverrideSource = source
+ updateDozeAmount()
+ }
+
+ private fun clearHardDozeAmountOverride(): Boolean {
+ if (hardDozeAmountOverride == null) return false
+ hardDozeAmountOverride = null
+ hardDozeAmountOverrideSource = "Cleared: $hardDozeAmountOverrideSource"
+ updateDozeAmount()
+ return true
+ }
+
+ private fun updateDozeAmount() {
+ // Calculate new doze amount (linear)
+ val newOutputLinearDozeAmount =
+ hardDozeAmountOverride ?: max(inputLinearDozeAmount, delayedDozeAmountOverride)
+ val changed = outputLinearDozeAmount != newOutputLinearDozeAmount
+
+ // notify when the animation is starting
+ if (
+ newOutputLinearDozeAmount != 1.0f &&
+ newOutputLinearDozeAmount != 0.0f &&
+ (outputLinearDozeAmount == 0.0f || outputLinearDozeAmount == 1.0f)
+ ) {
+ // Let's notify the scroller that an animation started
+ notifyAnimationStart(outputLinearDozeAmount == 1.0f)
+ }
+
+ // Update output doze amount
+ outputLinearDozeAmount = newOutputLinearDozeAmount
+ outputEasedDozeAmount = dozeAmountInterpolator.getInterpolation(outputLinearDozeAmount)
+ logger.logUpdateDozeAmount(
+ inputLinear = inputLinearDozeAmount,
+ delayLinear = delayedDozeAmountOverride,
+ hardOverride = hardDozeAmountOverride,
+ outputLinear = outputLinearDozeAmount,
+ state = statusBarStateController.state,
+ changed = changed
+ )
+ mStackScrollerController.setDozeAmount(outputEasedDozeAmount)
updateHideAmount()
- if (changed && linear == 0.0f) {
+ if (changed && outputLinearDozeAmount == 0.0f) {
setNotificationsVisible(visible = false, animate = false, increaseSpeed = false)
- setNotificationsVisibleForExpansion(visible = false, animate = false,
- increaseSpeed = false)
+ setNotificationsVisibleForExpansion(
+ visible = false,
+ animate = false,
+ increaseSpeed = false
+ )
+ }
+ }
+
+ /**
+ * Notifies the wakeup coordinator that we're waking up.
+ *
+ * [requestDelayedAnimation] is used to request that we delay the start of the wakeup animation
+ * in order to wait for a potential fingerprint authentication to arrive, since unlocking during
+ * the wakeup animation looks chaotic.
+ *
+ * If called with [wakingUp] and [requestDelayedAnimation] both `true`, the [WakeUpListener]s
+ * are guaranteed to receive at least one [WakeUpListener.onDelayedDozeAmountAnimationRunning]
+ * call with `false` at some point in the near future. A call with `true` before that will
+ * happen if the animation is not already running.
+ */
+ fun setWakingUp(
+ wakingUp: Boolean,
+ requestDelayedAnimation: Boolean,
+ ) {
+ logger.logSetWakingUp(wakingUp, requestDelayedAnimation)
+ this.wakingUp = wakingUp
+ if (wakingUp && requestDelayedAnimation) {
+ scheduleDelayedDozeAmountAnimation()
}
}
+ private fun scheduleDelayedDozeAmountAnimation() {
+ val alreadyRunning = delayedDozeAmountAnimator != null
+ logger.logStartDelayedDozeAmountAnimation(alreadyRunning)
+ if (alreadyRunning) return
+ delayedDozeAmount.setValue(this, 1.0f)
+ delayedDozeAmountAnimator =
+ ObjectAnimator.ofFloat(this, delayedDozeAmount, 0.0f).apply {
+ interpolator = InterpolatorsAndroidX.LINEAR
+ duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP.toLong()
+ startDelay = WAKEUP_ANIMATION_DELAY_MS.toLong()
+ doOnStart {
+ wakeUpListeners.forEach { it.onDelayedDozeAmountAnimationRunning(true) }
+ }
+ doOnEnd {
+ delayedDozeAmountAnimator = null
+ wakeUpListeners.forEach { it.onDelayedDozeAmountAnimationRunning(false) }
+ }
+ start()
+ }
+ }
+
override fun onStateChanged(newState: Int) {
logger.logOnStateChanged(newState = newState, storedState = state)
if (state == StatusBarState.SHADE && newState == StatusBarState.SHADE) {
@@ -302,12 +403,15 @@ class NotificationWakeUpCoordinator @Inject constructor(
// undefined state, so it's an indication that we should do state cleanup. We override
// the doze amount to 0f (not dozing) so that the notifications are no longer hidden.
// See: UnlockedScreenOffAnimationController.onFinishedWakingUp()
- setDozeAmount(0f, 0f, source = "Override: Shade->Shade (lock cancelled by unlock)")
+ setHardDozeAmountOverride(
+ dozing = false,
+ source = "Override: Shade->Shade (lock cancelled by unlock)"
+ )
this.state = newState
return
}
- if (overrideDozeAmountIfAnimatingScreenOff(mLinearDozeAmount)) {
+ if (overrideDozeAmountIfAnimatingScreenOff()) {
this.state = newState
return
}
@@ -317,7 +421,7 @@ class NotificationWakeUpCoordinator @Inject constructor(
return
}
- maybeClearDozeAmountOverrideHidingNotifs()
+ maybeClearHardDozeAmountOverrideHidingNotifs()
this.state = newState
}
@@ -340,15 +444,14 @@ class NotificationWakeUpCoordinator @Inject constructor(
/**
* @return Whether the doze amount was overridden because bypass is enabled. If true, the
- * original doze amount should be ignored.
+ * original doze amount should be ignored.
*/
private fun overrideDozeAmountIfBypass(): Boolean {
if (bypassController.bypassEnabled) {
if (statusBarStateController.state == StatusBarState.KEYGUARD) {
- setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)",
- hidesNotifsByOverride = true)
+ setHardDozeAmountOverride(dozing = true, source = "Override: bypass (keyguard)")
} else {
- setDozeAmount(0f, 0f, source = "Override: bypass (shade)")
+ setHardDozeAmountOverride(dozing = false, source = "Override: bypass (shade)")
}
return true
}
@@ -362,26 +465,28 @@ class NotificationWakeUpCoordinator @Inject constructor(
* This fixes bugs where the bypass state changing could result in stale overrides, hiding
* notifications either on the inside screen or even after unlock.
*/
- private fun maybeClearDozeAmountOverrideHidingNotifs() {
- if (mNotifsHiddenByDozeAmountOverride) {
+ private fun maybeClearHardDozeAmountOverrideHidingNotifs() {
+ if (hardDozeAmountOverride == 1f) {
val onKeyguard = statusBarStateController.state == StatusBarState.KEYGUARD
val dozing = statusBarStateController.isDozing
val bypass = bypassController.bypassEnabled
val animating =
- screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()
+ screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()
// Overrides are set by [overrideDozeAmountIfAnimatingScreenOff] and
// [overrideDozeAmountIfBypass] based on 'animating' and 'bypass' respectively, so only
// clear the override if both those conditions are cleared. But also require either
// !dozing or !onKeyguard because those conditions should indicate that we intend
// notifications to be visible, and thus it is safe to unhide them.
val willRemove = (!onKeyguard || !dozing) && !bypass && !animating
- logger.logMaybeClearDozeAmountOverrideHidingNotifs(
- willRemove = willRemove,
- onKeyguard = onKeyguard, dozing = dozing,
- bypass = bypass, animating = animating,
+ logger.logMaybeClearHardDozeAmountOverrideHidingNotifs(
+ willRemove = willRemove,
+ onKeyguard = onKeyguard,
+ dozing = dozing,
+ bypass = bypass,
+ animating = animating,
)
if (willRemove) {
- setDozeAmount(0f, 0f, source = "Removed: $mDozeAmountSource")
+ clearHardDozeAmountOverride()
}
}
}
@@ -392,12 +497,11 @@ class NotificationWakeUpCoordinator @Inject constructor(
* off and dozeAmount goes from 1f to 0f.
*
* @return Whether the doze amount was overridden because we are playing the screen off
- * animation. If true, the original doze amount should be ignored.
+ * animation. If true, the original doze amount should be ignored.
*/
- private fun overrideDozeAmountIfAnimatingScreenOff(linearDozeAmount: Float): Boolean {
+ private fun overrideDozeAmountIfAnimatingScreenOff(): Boolean {
if (screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()) {
- setDozeAmount(1f, 1f, source = "Override: animating screen off",
- hidesNotifsByOverride = true)
+ setHardDozeAmountOverride(dozing = true, source = "Override: animating screen off")
return true
}
@@ -406,41 +510,41 @@ class NotificationWakeUpCoordinator @Inject constructor(
private fun startVisibilityAnimation(increaseSpeed: Boolean) {
if (mNotificationVisibleAmount == 0f || mNotificationVisibleAmount == 1f) {
- mVisibilityInterpolator = if (mNotificationsVisible)
- Interpolators.TOUCH_RESPONSE
- else
- Interpolators.FAST_OUT_SLOW_IN_REVERSE
+ mVisibilityInterpolator =
+ if (mNotificationsVisible) Interpolators.TOUCH_RESPONSE
+ else Interpolators.FAST_OUT_SLOW_IN_REVERSE
}
val target = if (mNotificationsVisible) 1.0f else 0.0f
- val visibilityAnimator = ObjectAnimator.ofFloat(this, mNotificationVisibility, target)
- visibilityAnimator.setInterpolator(Interpolators.LINEAR)
+ val visibilityAnimator = ObjectAnimator.ofFloat(this, notificationVisibility, target)
+ visibilityAnimator.interpolator = InterpolatorsAndroidX.LINEAR
var duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP.toLong()
if (increaseSpeed) {
duration = (duration.toFloat() / 1.5F).toLong()
}
- visibilityAnimator.setDuration(duration)
+ visibilityAnimator.duration = duration
visibilityAnimator.start()
mVisibilityAnimator = visibilityAnimator
}
private fun setVisibilityAmount(visibilityAmount: Float) {
+ logger.logSetVisibilityAmount(visibilityAmount)
mLinearVisibilityAmount = visibilityAmount
- mVisibilityAmount = mVisibilityInterpolator.getInterpolation(
- visibilityAmount)
+ mVisibilityAmount = mVisibilityInterpolator.getInterpolation(visibilityAmount)
handleAnimationFinished()
updateHideAmount()
}
private fun handleAnimationFinished() {
- if (mLinearDozeAmount == 0.0f || mLinearVisibilityAmount == 0.0f) {
+ if (outputLinearDozeAmount == 0.0f || mLinearVisibilityAmount == 0.0f) {
mEntrySetToClearWhenFinished.forEach { it.setHeadsUpAnimatingAway(false) }
mEntrySetToClearWhenFinished.clear()
}
}
private fun updateHideAmount() {
- val linearAmount = min(1.0f - mLinearVisibilityAmount, mLinearDozeAmount)
- val amount = min(1.0f - mVisibilityAmount, mDozeAmount)
+ val linearAmount = min(1.0f - mLinearVisibilityAmount, outputLinearDozeAmount)
+ val amount = min(1.0f - mVisibilityAmount, outputEasedDozeAmount)
+ logger.logSetHideAmount(linearAmount)
mStackScrollerController.setHideAmount(linearAmount, amount)
notificationsFullyHidden = linearAmount == 1.0f
}
@@ -458,7 +562,7 @@ class NotificationWakeUpCoordinator @Inject constructor(
override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) {
var animate = shouldAnimateVisibility()
if (!isHeadsUp) {
- if (mLinearDozeAmount != 0.0f && mLinearVisibilityAmount != 0.0f) {
+ if (outputLinearDozeAmount != 0.0f && mLinearVisibilityAmount != 0.0f) {
if (entry.isRowDismissed) {
// if we animate, we see the shelf briefly visible. Instead we fully animate
// the notification and its background out
@@ -477,13 +581,16 @@ class NotificationWakeUpCoordinator @Inject constructor(
}
private fun shouldAnimateVisibility() =
- dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking
+ dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking
override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println("mLinearDozeAmount: $mLinearDozeAmount")
- pw.println("mDozeAmount: $mDozeAmount")
- pw.println("mDozeAmountSource: $mDozeAmountSource")
- pw.println("mNotifsHiddenByDozeAmountOverride: $mNotifsHiddenByDozeAmountOverride")
+ pw.println("inputLinearDozeAmount: $inputLinearDozeAmount")
+ pw.println("inputEasedDozeAmount: $inputEasedDozeAmount")
+ pw.println("delayedDozeAmountOverride: $delayedDozeAmountOverride")
+ pw.println("hardDozeAmountOverride: $hardDozeAmountOverride")
+ pw.println("hardDozeAmountOverrideSource: $hardDozeAmountOverrideSource")
+ pw.println("outputLinearDozeAmount: $outputLinearDozeAmount")
+ pw.println("outputEasedDozeAmount: $outputEasedDozeAmount")
pw.println("mNotificationVisibleAmount: $mNotificationVisibleAmount")
pw.println("mNotificationsVisible: $mNotificationsVisible")
pw.println("mNotificationsVisibleForExpansion: $mNotificationsVisibleForExpansion")
@@ -500,16 +607,53 @@ class NotificationWakeUpCoordinator @Inject constructor(
pw.println("canShowPulsingHuns: $canShowPulsingHuns")
}
+ fun logDelayingClockWakeUpAnimation(delayingAnimation: Boolean) {
+ logger.logDelayingClockWakeUpAnimation(delayingAnimation)
+ }
+
interface WakeUpListener {
- /**
- * Called whenever the notifications are fully hidden or shown
- */
+ /** Called whenever the notifications are fully hidden or shown */
@JvmDefault fun onFullyHiddenChanged(isFullyHidden: Boolean) {}
/**
* Called whenever the pulseExpansion changes
+ *
* @param expandingChanged if the user has started or stopped expanding
*/
@JvmDefault fun onPulseExpansionChanged(expandingChanged: Boolean) {}
+
+ /**
+ * Called when the animator started by [scheduleDelayedDozeAmountAnimation] begins running
+ * after the start delay, or after it ends/is cancelled.
+ */
+ @JvmDefault fun onDelayedDozeAmountAnimationRunning(running: Boolean) {}
+ }
+
+ companion object {
+ private val notificationVisibility =
+ object : FloatProperty<NotificationWakeUpCoordinator>("notificationVisibility") {
+
+ override fun setValue(coordinator: NotificationWakeUpCoordinator, value: Float) {
+ coordinator.setVisibilityAmount(value)
+ }
+
+ override fun get(coordinator: NotificationWakeUpCoordinator): Float {
+ return coordinator.mLinearVisibilityAmount
+ }
+ }
+
+ private val delayedDozeAmount =
+ object : FloatProperty<NotificationWakeUpCoordinator>("delayedDozeAmount") {
+
+ override fun setValue(coordinator: NotificationWakeUpCoordinator, value: Float) {
+ coordinator.delayedDozeAmountOverride = value
+ coordinator.logger.logSetDelayDozeAmountOverride(value)
+ coordinator.updateDozeAmount()
+ }
+
+ override fun get(coordinator: NotificationWakeUpCoordinator): Float {
+ return coordinator.delayedDozeAmountOverride
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
index 88d9ffcdcf3e..dd3c2a9df3e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
@@ -22,50 +22,75 @@ import javax.inject.Inject
class NotificationWakeUpCoordinatorLogger
@Inject
constructor(@NotificationLockscreenLog private val buffer: LogBuffer) {
- private var lastSetDozeAmountLogWasFractional = false
+ private var allowThrottle = true
+ private var lastSetDozeAmountLogInputWasFractional = false
+ private var lastSetDozeAmountLogDelayWasFractional = false
private var lastSetDozeAmountLogState = -1
- private var lastSetDozeAmountLogSource = "undefined"
+ private var lastSetHardOverride: Float? = null
private var lastOnDozeAmountChangedLogWasFractional = false
+ private var lastSetDelayDozeAmountOverrideLogWasFractional = false
+ private var lastSetVisibilityAmountLogWasFractional = false
+ private var lastSetHideAmountLogWasFractional = false
+ private var lastSetHideAmount = -1f
- fun logSetDozeAmount(
- linear: Float,
- eased: Float,
- source: String,
+ fun logUpdateDozeAmount(
+ inputLinear: Float,
+ delayLinear: Float,
+ hardOverride: Float?,
+ outputLinear: Float,
state: Int,
changed: Boolean,
) {
// Avoid logging on every frame of the animation if important values are not changing
- val isFractional = linear != 1f && linear != 0f
+ val isInputFractional = inputLinear != 1f && inputLinear != 0f
+ val isDelayFractional = delayLinear != 1f && delayLinear != 0f
if (
- lastSetDozeAmountLogWasFractional &&
- isFractional &&
+ (isInputFractional || isDelayFractional) &&
+ lastSetDozeAmountLogInputWasFractional == isInputFractional &&
+ lastSetDozeAmountLogDelayWasFractional == isDelayFractional &&
lastSetDozeAmountLogState == state &&
- lastSetDozeAmountLogSource == source
+ lastSetHardOverride == hardOverride &&
+ allowThrottle
) {
return
}
- lastSetDozeAmountLogWasFractional = isFractional
+ lastSetDozeAmountLogInputWasFractional = isInputFractional
+ lastSetDozeAmountLogDelayWasFractional = isDelayFractional
lastSetDozeAmountLogState = state
- lastSetDozeAmountLogSource = source
+ lastSetHardOverride = hardOverride
buffer.log(
TAG,
DEBUG,
{
- double1 = linear.toDouble()
- str2 = eased.toString()
- str3 = source
+ double1 = inputLinear.toDouble()
+ str1 = hardOverride.toString()
+ str2 = outputLinear.toString()
+ str3 = delayLinear.toString()
int1 = state
bool1 = changed
},
{
- "setDozeAmount(linear=$double1, eased=$str2, source=$str3)" +
+ "updateDozeAmount() inputLinear=$double1 delayLinear=$str3" +
+ " hardOverride=$str1 outputLinear=$str2" +
" state=${StatusBarState.toString(int1)} changed=$bool1"
}
)
}
- fun logMaybeClearDozeAmountOverrideHidingNotifs(
+ fun logSetDozeAmountOverride(dozing: Boolean, source: String) {
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ bool1 = dozing
+ str1 = source
+ },
+ { "setDozeAmountOverride(dozing=$bool1, source=\"$str1\")" }
+ )
+ }
+
+ fun logMaybeClearHardDozeAmountOverrideHidingNotifs(
willRemove: Boolean,
onKeyguard: Boolean,
dozing: Boolean,
@@ -80,14 +105,14 @@ constructor(@NotificationLockscreenLog private val buffer: LogBuffer) {
"willRemove=$willRemove onKeyguard=$onKeyguard dozing=$dozing" +
" bypass=$bypass animating=$animating"
},
- { "maybeClearDozeAmountOverrideHidingNotifs() $str1" }
+ { "maybeClearHardDozeAmountOverrideHidingNotifs() $str1" }
)
}
fun logOnDozeAmountChanged(linear: Float, eased: Float) {
// Avoid logging on every frame of the animation when values are fractional
val isFractional = linear != 1f && linear != 0f
- if (lastOnDozeAmountChangedLogWasFractional && isFractional) return
+ if (lastOnDozeAmountChangedLogWasFractional && isFractional && allowThrottle) return
lastOnDozeAmountChangedLogWasFractional = isFractional
buffer.log(
TAG,
@@ -100,6 +125,47 @@ constructor(@NotificationLockscreenLog private val buffer: LogBuffer) {
)
}
+ fun logSetDelayDozeAmountOverride(linear: Float) {
+ // Avoid logging on every frame of the animation when values are fractional
+ val isFractional = linear != 1f && linear != 0f
+ if (lastSetDelayDozeAmountOverrideLogWasFractional && isFractional && allowThrottle) return
+ lastSetDelayDozeAmountOverrideLogWasFractional = isFractional
+ buffer.log(
+ TAG,
+ DEBUG,
+ { double1 = linear.toDouble() },
+ { "setDelayDozeAmountOverride($double1)" }
+ )
+ }
+
+ fun logSetVisibilityAmount(linear: Float) {
+ // Avoid logging on every frame of the animation when values are fractional
+ val isFractional = linear != 1f && linear != 0f
+ if (lastSetVisibilityAmountLogWasFractional && isFractional && allowThrottle) return
+ lastSetVisibilityAmountLogWasFractional = isFractional
+ buffer.log(TAG, DEBUG, { double1 = linear.toDouble() }, { "setVisibilityAmount($double1)" })
+ }
+
+ fun logSetHideAmount(linear: Float) {
+ // Avoid logging the same value repeatedly
+ if (lastSetHideAmount == linear && allowThrottle) return
+ lastSetHideAmount = linear
+ // Avoid logging on every frame of the animation when values are fractional
+ val isFractional = linear != 1f && linear != 0f
+ if (lastSetHideAmountLogWasFractional && isFractional && allowThrottle) return
+ lastSetHideAmountLogWasFractional = isFractional
+ buffer.log(TAG, DEBUG, { double1 = linear.toDouble() }, { "setHideAmount($double1)" })
+ }
+
+ fun logStartDelayedDozeAmountAnimation(alreadyRunning: Boolean) {
+ buffer.log(
+ TAG,
+ DEBUG,
+ { bool1 = alreadyRunning },
+ { "startDelayedDozeAmountAnimation() alreadyRunning=$bool1" }
+ )
+ }
+
fun logOnStateChanged(newState: Int, storedState: Int) {
buffer.log(
TAG,
@@ -114,6 +180,27 @@ constructor(@NotificationLockscreenLog private val buffer: LogBuffer) {
}
)
}
+
+ fun logSetWakingUp(wakingUp: Boolean, requestDelayedAnimation: Boolean) {
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ bool1 = wakingUp
+ bool2 = requestDelayedAnimation
+ },
+ { "setWakingUp(wakingUp=$bool1, requestDelayedAnimation=$bool2)" }
+ )
+ }
+
+ fun logDelayingClockWakeUpAnimation(delayingAnimation: Boolean) {
+ buffer.log(
+ TAG,
+ DEBUG,
+ { bool1 = delayingAnimation },
+ { "logDelayingClockWakeUpAnimation($bool1)" }
+ )
+ }
}
private const val TAG = "NotificationWakeUpCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index 6c84fefe2d38..ea5cb308a2d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -190,7 +190,9 @@ public class RankingCoordinator implements Coordinator {
"DndSuppressingVisualEffects") {
@Override
public boolean shouldFilterOut(NotificationEntry entry, long now) {
- if (mStatusBarStateController.isDozing() && entry.shouldSuppressAmbient()) {
+ if ((mStatusBarStateController.isDozing()
+ || mStatusBarStateController.getDozeAmount() == 1f)
+ && entry.shouldSuppressAmbient()) {
return true;
}
@@ -200,6 +202,20 @@ public class RankingCoordinator implements Coordinator {
private final StatusBarStateController.StateListener mStatusBarStateCallback =
new StatusBarStateController.StateListener() {
+ private boolean mPrevDozeAmountIsOne = false;
+
+ @Override
+ public void onDozeAmountChanged(float linear, float eased) {
+ StatusBarStateController.StateListener.super.onDozeAmountChanged(linear, eased);
+
+ boolean dozeAmountIsOne = linear == 1f;
+ if (mPrevDozeAmountIsOne != dozeAmountIsOne) {
+ mDndVisualEffectsFilter.invalidateList("dozeAmount changed to "
+ + (dozeAmountIsOne ? "one" : "not one"));
+ mPrevDozeAmountIsOne = dozeAmountIsOne;
+ }
+ }
+
@Override
public void onDozingChanged(boolean isDozing) {
mDndVisualEffectsFilter.invalidateList("onDozingChanged to " + isDozing);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index a2de3c38d090..e47e4146d4c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5638,6 +5638,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
public void setDozeAmount(float dozeAmount) {
mAmbientState.setDozeAmount(dozeAmount);
updateContinuousBackgroundDrawing();
+ updateStackPosition();
requestChildrenUpdate();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 7f8c1351aa7a..9272f06076fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -53,6 +53,7 @@ import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.statusbar.LightRevealScrim;
import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.util.Compile;
import java.io.PrintWriter;
@@ -71,6 +72,7 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn
boolean DEBUG_MEDIA_FAKE_ARTWORK = false;
boolean DEBUG_CAMERA_LIFT = false;
boolean DEBUG_WINDOW_STATE = false;
+ boolean DEBUG_WAKEUP_DELAY = Compile.IS_DEBUG;
// additional instrumentation for testing purposes; intended to be left on during development
boolean CHATTY = DEBUG;
boolean SHOW_LOCKSCREEN_MEDIA_ARTWORK = true;
@@ -536,6 +538,8 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn
void extendDozePulse();
+ boolean shouldDelayWakeUpAnimation();
+
public static class KeyboardShortcutsMessage {
final int mDeviceId;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 06b3237e4054..8b6617b8f2e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -67,12 +67,12 @@ import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
+import dagger.Lazy;
+
import java.util.Optional;
import javax.inject.Inject;
-import dagger.Lazy;
-
/** */
@CentralSurfacesComponent.CentralSurfacesScope
public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callbacks {
@@ -218,7 +218,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
return;
}
- mNotificationPanelViewController.expandShadeToNotifications();
+ mNotificationPanelViewController.expandToNotifications();
}
@Override
@@ -234,7 +234,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
// Settings are not available in setup
if (!mDeviceProvisionedController.isCurrentUserSetup()) return;
- mNotificationPanelViewController.expandWithQs();
+ mNotificationPanelViewController.expandToQs();
}
@Override
@@ -300,7 +300,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
}
}
- mNotificationPanelViewController.disable(state1, state2, animate);
+ mNotificationPanelViewController.disableHeader(state1, state2, animate);
}
/**
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 2bc09a100976..aabe0cb9794c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -67,6 +67,7 @@ import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.graphics.Point;
import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.fingerprint.FingerprintManager;
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.Binder;
@@ -262,6 +263,7 @@ import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
+import javax.inject.Provider;
/**
* A class handling initialization and coordination between some of the key central surfaces in
@@ -453,10 +455,20 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@VisibleForTesting
DozeServiceHost mDozeServiceHost;
- private boolean mWakeUpComingFromTouch;
private LightRevealScrim mLightRevealScrim;
private PowerButtonReveal mPowerButtonReveal;
+ private boolean mWakeUpComingFromTouch;
+
+ /**
+ * Whether we should delay the wakeup animation (which shows the notifications and moves the
+ * clock view). This is typically done when waking up from a 'press to unlock' gesture on a
+ * device with a side fingerprint sensor, so that if the fingerprint scan is successful, we
+ * can play the unlock animation directly rather than interrupting the wakeup animation part
+ * way through.
+ */
+ private boolean mShouldDelayWakeUpAnimation = false;
+
private final Object mQueueLock = new Object();
private final PulseExpansionHandler mPulseExpansionHandler;
@@ -518,6 +530,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private final MessageRouter mMessageRouter;
private final WallpaperManager mWallpaperManager;
private final UserTracker mUserTracker;
+ private final Provider<FingerprintManager> mFingerprintManager;
private CentralSurfacesComponent mCentralSurfacesComponent;
@@ -683,7 +696,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
@Override
public void onBackProgressed(BackEvent event) {
if (shouldBackBeHandled()) {
- if (mNotificationPanelViewController.canPanelBeCollapsed()) {
+ if (mNotificationPanelViewController.canBeCollapsed()) {
float fraction = event.getProgress();
mNotificationPanelViewController.onBackProgressed(fraction);
}
@@ -790,7 +803,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
Lazy<CameraLauncher> cameraLauncherLazy,
Lazy<LightRevealScrimViewModel> lightRevealScrimViewModelLazy,
AlternateBouncerInteractor alternateBouncerInteractor,
- UserTracker userTracker
+ UserTracker userTracker,
+ Provider<FingerprintManager> fingerprintManager
) {
mContext = context;
mNotificationsController = notificationsController;
@@ -873,6 +887,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mCameraLauncherLazy = cameraLauncherLazy;
mAlternateBouncerInteractor = alternateBouncerInteractor;
mUserTracker = userTracker;
+ mFingerprintManager = fingerprintManager;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mStartingSurfaceOptional = startingSurfaceOptional;
@@ -1255,14 +1270,13 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
// re-display the notification panel if necessary (for example, if
// a heads-up notification was being displayed and should continue being
// displayed).
- mNotificationPanelViewController.updatePanelExpansionAndVisibility();
+ mNotificationPanelViewController.updateExpansionAndVisibility();
setBouncerShowingForStatusBarComponents(mBouncerShowing);
checkBarModes();
});
initializer.initializeStatusBar(mCentralSurfacesComponent);
mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView);
- mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
createNavigationBar(result);
@@ -1335,7 +1349,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
this,
mGestureRec,
mShadeController::makeExpandedInvisible,
- mNotificationShelfController);
+ mNotificationShelfController,
+ mHeadsUpManager);
BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front),
@@ -2073,16 +2088,16 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
if (start) {
- mNotificationPanelViewController.startWaitingForOpenPanelGesture();
+ mNotificationPanelViewController.startWaitingForExpandGesture();
} else {
- mNotificationPanelViewController.stopWaitingForOpenPanelGesture(cancel, velocity);
+ mNotificationPanelViewController.stopWaitingForExpandGesture(cancel, velocity);
}
}
@Override
public void animateCollapseQuickSettings() {
if (mState == StatusBarState.SHADE) {
- mNotificationPanelViewController.collapsePanel(
+ mNotificationPanelViewController.collapse(
true, false /* delayed */, 1.0f /* speedUpFactor */);
}
}
@@ -3181,6 +3196,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
}
+ public boolean shouldDelayWakeUpAnimation() {
+ return mShouldDelayWakeUpAnimation;
+ }
+
private void updateDozingState() {
Trace.traceCounter(Trace.TRACE_TAG_APP, "dozing", mDozing ? 1 : 0);
Trace.beginSection("CentralSurfaces#updateDozingState");
@@ -3191,11 +3210,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
boolean keyguardVisibleOrWillBe =
keyguardVisible || (mDozing && mDozeParameters.shouldDelayKeyguardShow());
- boolean wakeAndUnlock = mBiometricUnlockController.getMode()
- == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
- boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup() && !wakeAndUnlock)
- || (mDozing && mDozeParameters.shouldControlScreenOff()
- && keyguardVisibleOrWillBe);
+ boolean animate = (!mDozing && shouldAnimateDozeWakeup())
+ || (mDozing && mDozeParameters.shouldControlScreenOff() && keyguardVisibleOrWillBe);
mNotificationPanelViewController.setDozing(mDozing, animate);
updateQsExpansionEnabled();
@@ -3277,14 +3293,14 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
return true;
}
if (mQsController.getExpanded()) {
- mNotificationPanelViewController.animateCloseQs(false);
+ mNotificationPanelViewController.animateCollapseQs(false);
return true;
}
if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) {
return true;
}
if (shouldBackBeHandled()) {
- if (mNotificationPanelViewController.canPanelBeCollapsed()) {
+ if (mNotificationPanelViewController.canBeCollapsed()) {
// this is the Shade dismiss animation, so make sure QQS closes when it ends.
mNotificationPanelViewController.onBackPressed();
mShadeController.animateCollapseShade();
@@ -3550,7 +3566,44 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
DejankUtils.startDetectingBlockingIpcs(tag);
mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
mDeviceInteractive = true;
- mWakeUpCoordinator.setWakingUp(true);
+
+ if (shouldAnimateDozeWakeup()) {
+ // If this is false, the power button must be physically pressed in order to
+ // trigger fingerprint authentication.
+ final boolean touchToUnlockAnytime = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
+ -1,
+ mUserTracker.getUserId()) > 0;
+
+ // Delay if we're waking up, not mid-doze animation (which means we are
+ // cancelling a sleep), from the power button, on a device with a power button
+ // FPS, and 'press to unlock' is required.
+ mShouldDelayWakeUpAnimation =
+ !isPulsing()
+ && mStatusBarStateController.getDozeAmount() == 1f
+ && mWakefulnessLifecycle.getLastWakeReason()
+ == PowerManager.WAKE_REASON_POWER_BUTTON
+ && mFingerprintManager.get().isPowerbuttonFps()
+ && mFingerprintManager.get().hasEnrolledFingerprints()
+ && !touchToUnlockAnytime;
+ if (DEBUG_WAKEUP_DELAY) {
+ Log.d(TAG, "mShouldDelayWakeUpAnimation=" + mShouldDelayWakeUpAnimation);
+ }
+ } else {
+ // If we're not animating anyway, we do not need to delay it.
+ mShouldDelayWakeUpAnimation = false;
+ if (DEBUG_WAKEUP_DELAY) {
+ Log.d(TAG, "mShouldDelayWakeUpAnimation CLEARED");
+ }
+ }
+
+ mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(
+ mShouldDelayWakeUpAnimation);
+ mWakeUpCoordinator.setWakingUp(
+ /* wakingUp= */ true,
+ mShouldDelayWakeUpAnimation);
+
if (!mKeyguardBypassController.getBypassEnabled()) {
mHeadsUpManager.releaseAllImmediately();
}
@@ -3577,7 +3630,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
@Override
public void onFinishedWakingUp() {
mWakeUpCoordinator.setFullyAwake(true);
- mWakeUpCoordinator.setWakingUp(false);
+ mWakeUpCoordinator.setWakingUp(false, false);
if (mKeyguardStateController.isOccluded()
&& !mDozeParameters.canControlUnlockedScreenOff()) {
// When the keyguard is occluded we don't use the KEYGUARD state which would
@@ -4317,7 +4370,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mNavigationBarController.touchAutoDim(mDisplayId);
Trace.beginSection("CentralSurfaces#updateKeyguardState");
if (mState == StatusBarState.KEYGUARD) {
- mNotificationPanelViewController.cancelPendingPanelCollapse();
+ mNotificationPanelViewController.cancelPendingCollapse();
}
updateDozingState();
checkBarModes();
@@ -4450,4 +4503,15 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
return mUserTracker.getUserHandle();
}
+
+ /**
+ * Whether we want to animate the wake animation AOD to lockscreen. This is done only if the
+ * doze service host says we can, and also we're not wake and unlocking (in which case the
+ * AOD instantly hides).
+ */
+ private boolean shouldAnimateDozeWakeup() {
+ return mDozeServiceHost.shouldAnimateWakeup()
+ && mBiometricUnlockController.getMode()
+ != BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 69f7c71dafba..171e3d0a864e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -32,6 +32,7 @@ import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeHeadsUpTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.HeadsUpStatusBarView;
@@ -133,7 +134,8 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
// has started pulling down the notification shade from the HUN and then the font size
// changes). We need to re-fetch these values since they're used to correctly display the
// HUN during this shade expansion.
- mTrackedChild = notificationPanelViewController.getTrackedHeadsUpNotification();
+ mTrackedChild = notificationPanelViewController.getShadeHeadsUpTracker()
+ .getTrackedHeadsUpNotification();
mAppearFraction = stackScrollerController.getAppearFraction();
mExpandedHeight = stackScrollerController.getExpandedHeight();
@@ -170,19 +172,23 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
mView.setOnDrawingRectChangedListener(
() -> updateIsolatedIconLocation(true /* requireUpdate */));
mWakeUpCoordinator.addListener(this);
- mNotificationPanelViewController.addTrackingHeadsUpListener(mSetTrackingHeadsUp);
- mNotificationPanelViewController.setHeadsUpAppearanceController(this);
+ getShadeHeadsUpTracker().addTrackingHeadsUpListener(mSetTrackingHeadsUp);
+ getShadeHeadsUpTracker().setHeadsUpAppearanceController(this);
mStackScrollerController.addOnExpandedHeightChangedListener(mSetExpandedHeight);
mDarkIconDispatcher.addDarkReceiver(this);
}
+ private ShadeHeadsUpTracker getShadeHeadsUpTracker() {
+ return mNotificationPanelViewController.getShadeHeadsUpTracker();
+ }
+
@Override
protected void onViewDetached() {
mHeadsUpManager.removeListener(this);
mView.setOnDrawingRectChangedListener(null);
mWakeUpCoordinator.removeListener(this);
- mNotificationPanelViewController.removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
- mNotificationPanelViewController.setHeadsUpAppearanceController(null);
+ getShadeHeadsUpTracker().removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
+ getShadeHeadsUpTracker().setHeadsUpAppearanceController(null);
mStackScrollerController.removeOnExpandedHeightChangedListener(mSetExpandedHeight);
mDarkIconDispatcher.removeDarkReceiver(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 3d6bebbe998c..a7413d58a6cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -65,18 +65,7 @@ public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener
mNotificationShadeWindowController.setHeadsUpShowing(true);
mStatusBarWindowController.setForceStatusBarVisible(true);
if (mNotificationPanelViewController.isFullyCollapsed()) {
- // We need to ensure that the touchable region is updated before the
- //window will be
- // resized, in order to not catch any touches. A layout will ensure that
- // onComputeInternalInsets will be called and after that we can
- //resize the layout. Let's
- // make sure that the window stays small for one frame until the
- //touchableRegion is set.
- mNotificationPanelViewController.requestLayoutOnView();
- mNotificationShadeWindowController.setForceWindowCollapsed(true);
- mNotificationPanelViewController.postToView(() -> {
- mNotificationShadeWindowController.setForceWindowCollapsed(false);
- });
+ mNotificationPanelViewController.updateTouchableRegion();
}
} else {
boolean bypassKeyguard = mKeyguardBypassController.getBypassEnabled()
@@ -96,7 +85,8 @@ public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener
//animation
// is finished.
mHeadsUpManager.setHeadsUpGoingAway(true);
- mNotificationPanelViewController.runAfterAnimationFinished(() -> {
+ mNotificationPanelViewController.getNotificationStackScrollLayoutController()
+ .runAfterAnimationFinished(() -> {
if (!mHeadsUpManager.hasPinnedHeadsUp()) {
mNotificationShadeWindowController.setHeadsUpShowing(false);
mHeadsUpManager.setHeadsUpGoingAway(false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index f9493f41dfd2..49b58df23fdb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -973,7 +973,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
public void onKeyguardFadedAway() {
mNotificationContainer.postDelayed(() -> mNotificationShadeWindowController
.setKeyguardFadingAway(false), 100);
- mNotificationPanelViewController.resetViewAlphas();
+ mNotificationPanelViewController.resetViewGroupFade();
mCentralSurfaces.finishKeyguardFadingAway();
mBiometricUnlockController.finishKeyguardFadingAway();
WindowManagerGlobal.getInstance().trimMemory(
@@ -1048,7 +1048,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (hideImmediately) {
mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
} else {
- mNotificationPanelViewController.expandShadeToNotifications();
+ mNotificationPanelViewController.expandToNotifications();
}
}
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 4eed48739b40..39362cf29e14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -16,7 +16,6 @@ package com.android.systemui.statusbar.phone;
import static com.android.systemui.statusbar.phone.CentralSurfaces.CLOSE_PANEL_WHEN_EMPTIED;
import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG;
-import static com.android.systemui.statusbar.phone.CentralSurfaces.MULTIUSER_DEBUG;
import android.app.KeyguardManager;
import android.content.Context;
@@ -176,7 +175,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
}
remoteInputManager.setUpWithCallback(
remoteInputManagerCallback,
- mNotificationPanel.createRemoteInputDelegate());
+ mNotificationPanel.getShadeNotificationPresenter().createRemoteInputDelegate());
initController.addPostInitTask(() -> {
mKeyguardIndicationController.init();
@@ -209,8 +208,8 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
}
private void maybeEndAmbientPulse() {
- if (mNotificationPanel.hasPulsingNotifications() &&
- !mHeadsUpManager.hasNotifications()) {
+ if (mNotificationPanel.getShadeNotificationPresenter().hasPulsingNotifications()
+ && !mHeadsUpManager.hasNotifications()) {
// We were showing a pulse for a notification, but no notifications are pulsing anymore.
// Finish the pulse.
mDozeScrimController.pulseOutNow();
@@ -222,7 +221,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
// Begin old BaseStatusBar.userSwitched
mHeadsUpManager.setUser(newUserId);
// End old BaseStatusBar.userSwitched
- if (MULTIUSER_DEBUG) mNotificationPanel.setHeaderDebugInfo("USER " + newUserId);
mCommandQueue.animateCollapsePanels();
mMediaManager.clearCurrentMediaNotification();
mCentralSurfaces.setLockscreenUser(newUserId);
@@ -243,7 +241,9 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
@Override
public void onActivated(ActivatableNotificationView view) {
onActivated();
- if (view != null) mNotificationPanel.setActivatedChild(view);
+ if (view != null) {
+ mNotificationPanel.getShadeNotificationPresenter().setActivatedChild(view);
+ }
}
public void onActivated() {
@@ -251,7 +251,8 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
MetricsEvent.ACTION_LS_NOTE,
0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_NOTIFICATION_FALSE_TOUCH);
- ActivatableNotificationView previousView = mNotificationPanel.getActivatedChild();
+ ActivatableNotificationView previousView =
+ mNotificationPanel.getShadeNotificationPresenter().getActivatedChild();
if (previousView != null) {
previousView.makeInactive(true /* animate */);
}
@@ -259,8 +260,8 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
@Override
public void onActivationReset(ActivatableNotificationView view) {
- if (view == mNotificationPanel.getActivatedChild()) {
- mNotificationPanel.setActivatedChild(null);
+ if (view == mNotificationPanel.getShadeNotificationPresenter().getActivatedChild()) {
+ mNotificationPanel.getShadeNotificationPresenter().setActivatedChild(null);
mKeyguardIndicationController.hideTransientIndication();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 24ddded8847a..fe639943e191 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -509,7 +509,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
private boolean shouldHideNotificationIcons() {
if (!mShadeExpansionStateManager.isClosed()
- && mNotificationPanelViewController.hideStatusBarIconsWhenExpanded()) {
+ && mNotificationPanelViewController.shouldHideStatusBarIconsWhenExpanded()) {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index adfea80715a2..eaa145582ba3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -37,6 +37,8 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIc
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyImpl
+import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxy
+import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxyImpl
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
@@ -65,8 +67,7 @@ abstract class StatusBarPipelineModule {
@Binds abstract fun wifiRepository(impl: WifiRepositorySwitcher): WifiRepository
- @Binds
- abstract fun wifiInteractor(impl: WifiInteractorImpl): WifiInteractor
+ @Binds abstract fun wifiInteractor(impl: WifiInteractorImpl): WifiInteractor
@Binds
abstract fun mobileConnectionsRepository(
@@ -78,6 +79,11 @@ abstract class StatusBarPipelineModule {
@Binds abstract fun mobileMappingsProxy(impl: MobileMappingsProxyImpl): MobileMappingsProxy
@Binds
+ abstract fun subscriptionManagerProxy(
+ impl: SubscriptionManagerProxyImpl
+ ): SubscriptionManagerProxy
+
+ @Binds
abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 8c93bf7c2198..45d50c103909 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -44,6 +44,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameMode
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
@@ -65,6 +66,7 @@ import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -76,6 +78,7 @@ class MobileConnectionsRepositoryImpl
constructor(
connectivityRepository: ConnectivityRepository,
private val subscriptionManager: SubscriptionManager,
+ private val subscriptionManagerProxy: SubscriptionManagerProxy,
private val telephonyManager: TelephonyManager,
private val logger: MobileInputLogger,
@MobileSummaryLog private val tableLogger: TableLogBuffer,
@@ -195,7 +198,7 @@ constructor(
override val defaultDataSubId: StateFlow<Int> =
broadcastDispatcher
.broadcastFlow(
- IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED),
) { intent, _ ->
intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
}
@@ -204,14 +207,11 @@ constructor(
tableLogger,
LOGGING_PREFIX,
columnName = "defaultSubId",
- initialValue = SubscriptionManager.getDefaultDataSubscriptionId(),
+ initialValue = INVALID_SUBSCRIPTION_ID,
)
+ .onStart { emit(subscriptionManagerProxy.getDefaultDataSubscriptionId()) }
.onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
- .stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- SubscriptionManager.getDefaultDataSubscriptionId()
- )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
private val carrierConfigChangedEvent =
broadcastDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/SubscriptionManagerProxy.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/SubscriptionManagerProxy.kt
new file mode 100644
index 000000000000..22d048343bc9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/SubscriptionManagerProxy.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.util
+
+import android.telephony.SubscriptionManager
+import javax.inject.Inject
+
+interface SubscriptionManagerProxy {
+ fun getDefaultDataSubscriptionId(): Int
+}
+
+/** Injectable proxy class for [SubscriptionManager]'s static methods */
+class SubscriptionManagerProxyImpl @Inject constructor() : SubscriptionManagerProxy {
+ /** The system default data subscription id, or INVALID_SUBSCRIPTION_ID on error */
+ override fun getDefaultDataSubscriptionId() = SubscriptionManager.getDefaultDataSubscriptionId()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 86e74564fba0..a4cb99b1b94b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -64,7 +64,7 @@ public class BrightnessMirrorController
mToggleSliderController = setMirrorLayout();
mNotificationPanel = notificationPanelViewController;
mDepthController = notificationShadeDepthController;
- mNotificationPanel.setPanelAlphaEndAction(() -> {
+ mNotificationPanel.setAlphaChangeAnimationEndAction(() -> {
mBrightnessMirror.setVisibility(View.INVISIBLE);
});
mVisibilityCallback = visibilityCallback;
@@ -74,13 +74,13 @@ public class BrightnessMirrorController
public void showMirror() {
mBrightnessMirror.setVisibility(View.VISIBLE);
mVisibilityCallback.accept(true);
- mNotificationPanel.setPanelAlpha(0, true /* animate */);
+ mNotificationPanel.setAlpha(0, true /* animate */);
mDepthController.setBrightnessMirrorVisible(true);
}
public void hideMirror() {
mVisibilityCallback.accept(false);
- mNotificationPanel.setPanelAlpha(255, true /* animate */);
+ mNotificationPanel.setAlpha(255, true /* animate */);
mDepthController.setBrightnessMirrorVisible(false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 1a4a311ee0de..9ede6ce29963 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -281,6 +281,10 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
mUser = user;
}
+ public int getUser() {
+ return mUser;
+ }
+
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("HeadsUpManager state:");
dumpInternal(pw, args);
diff --git a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java
index f09b2f76e38c..757b4e50c3f8 100644
--- a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java
@@ -108,13 +108,18 @@ public class TouchInsetManager {
private void updateTouchRegions() {
mExecutor.execute(() -> {
final HashMap<AttachedSurfaceControl, Region> affectedSurfaces = new HashMap<>();
+ if (mTrackedViews.isEmpty()) {
+ return;
+ }
+
mTrackedViews.stream().forEach(view -> {
- if (!view.isAttachedToWindow()) {
+ final AttachedSurfaceControl surface = view.getRootSurfaceControl();
+
+ // Detached views will not have a surface control.
+ if (surface == null) {
return;
}
- final AttachedSurfaceControl surface = view.getRootSurfaceControl();
-
if (!affectedSurfaces.containsKey(surface)) {
affectedSurfaces.put(surface, Region.obtain());
}
@@ -179,6 +184,7 @@ public class TouchInsetManager {
mSessionRegions.values().stream().forEach(regionMapping -> {
regionMapping.entrySet().stream().forEach(entry -> {
final AttachedSurfaceControl surface = entry.getKey();
+
if (!affectedSurfaces.containsKey(surface)) {
affectedSurfaces.put(surface, Region.obtain());
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index 101bd4483cb3..d1bd73a4fa5a 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -30,6 +30,7 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.shade.ShadeFoldAnimator
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.ScreenOffAnimation
@@ -79,7 +80,7 @@ constructor(
private val foldToAodLatencyTracker = FoldToAodLatencyTracker()
private val startAnimationRunnable = Runnable {
- centralSurfaces.notificationPanelViewController.startFoldToAodAnimation(
+ getShadeFoldAnimator().startFoldToAodAnimation(
/* startAction= */ { foldToAodLatencyTracker.onAnimationStarted() },
/* endAction= */ { setAnimationState(playing = false) },
/* cancelAction= */ { setAnimationState(playing = false) },
@@ -93,7 +94,7 @@ constructor(
wakefulnessLifecycle.addObserver(this)
// TODO(b/254878364): remove this call to NPVC.getView()
- centralSurfaces.notificationPanelViewController.view.repeatWhenAttached {
+ getShadeFoldAnimator().view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) { listenForDozing(this) }
}
}
@@ -109,7 +110,7 @@ constructor(
override fun startAnimation(): Boolean =
if (shouldStartAnimation()) {
setAnimationState(playing = true)
- centralSurfaces.notificationPanelViewController.prepareFoldToAodAnimation()
+ getShadeFoldAnimator().prepareFoldToAodAnimation()
true
} else {
setAnimationState(playing = false)
@@ -120,12 +121,15 @@ constructor(
if (isAnimationPlaying) {
foldToAodLatencyTracker.cancel()
cancelAnimation?.run()
- centralSurfaces.notificationPanelViewController.cancelFoldToAodAnimation()
+ getShadeFoldAnimator().cancelFoldToAodAnimation()
}
setAnimationState(playing = false)
}
+ private fun getShadeFoldAnimator(): ShadeFoldAnimator =
+ centralSurfaces.notificationPanelViewController.shadeFoldAnimator
+
private fun setAnimationState(playing: Boolean) {
shouldPlayAnimation = playing
isAnimationPlaying = playing
@@ -152,16 +156,17 @@ constructor(
} else if (isFolded && !isFoldHandled && alwaysOnEnabled && isDozing) {
// Screen turning on for the first time after folding and we are already dozing
// We should play the folding to AOD animation
+ isFoldHandled = true
setAnimationState(playing = true)
- centralSurfaces.notificationPanelViewController.prepareFoldToAodAnimation()
+ getShadeFoldAnimator().prepareFoldToAodAnimation()
// We don't need to wait for the scrim as it is already displayed
// but we should wait for the initial animation preparations to be drawn
// (setting initial alpha/translation)
// TODO(b/254878364): remove this call to NPVC.getView()
OneShotPreDrawListener.add(
- centralSurfaces.notificationPanelViewController.view,
+ getShadeFoldAnimator().view,
onReady
)
} else {
@@ -186,7 +191,10 @@ constructor(
cancelAnimation?.run()
// Post starting the animation to the next frame to avoid junk due to inset changes
- cancelAnimation = mainExecutor.executeDelayed(startAnimationRunnable, /* delayMillis= */ 0)
+ cancelAnimation = mainExecutor.executeDelayed(
+ startAnimationRunnable,
+ /* delayMillis= */ 0
+ )
shouldPlayAnimation = false
}
}
diff --git a/packages/SystemUI/tests/res/drawable-nodpi/romainguy_rockaway.jpg b/packages/SystemUI/tests/res/drawable-nodpi/romainguy_rockaway.jpg
new file mode 100644
index 000000000000..68473ba6c962
--- /dev/null
+++ b/packages/SystemUI/tests/res/drawable-nodpi/romainguy_rockaway.jpg
Binary files differ
diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java
new file mode 100644
index 000000000000..e93e86291535
--- /dev/null
+++ b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.animation;
+
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.AndroidRuntimeException;
+
+import androidx.annotation.NonNull;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * NOTE: this is a copy of the {@link androidx.core.animation.AnimatorTestRule} which attempts to
+ * circumvent the problems with {@link androidx.core.animation.AnimationHandler} having a static
+ * list of callbacks.
+ *
+ * TODO(b/275602127): remove this and use the original rule once we have the updated androidx code.
+ */
+public final class AnimatorTestRule2 implements TestRule {
+
+ class TestAnimationHandler extends AnimationHandler {
+ TestAnimationHandler() {
+ super(new TestProvider());
+ }
+
+ List<AnimationFrameCallback> animationCallbacks = new ArrayList<>();
+
+ @Override
+ void addAnimationFrameCallback(AnimationFrameCallback callback) {
+ animationCallbacks.add(callback);
+ callback.doAnimationFrame(getCurrentTime());
+ }
+
+ @Override
+ public void removeCallback(AnimationFrameCallback callback) {
+ int id = animationCallbacks.indexOf(callback);
+ if (id >= 0) {
+ animationCallbacks.set(id, null);
+ }
+ }
+
+ void onAnimationFrame(long frameTime) {
+ for (int i = 0; i < animationCallbacks.size(); i++) {
+ final AnimationFrameCallback callback = animationCallbacks.get(i);
+ if (callback == null) {
+ continue;
+ }
+ callback.doAnimationFrame(frameTime);
+ }
+ }
+
+ @Override
+ void autoCancelBasedOn(ObjectAnimator objectAnimator) {
+ for (int i = animationCallbacks.size() - 1; i >= 0; i--) {
+ AnimationFrameCallback cb = animationCallbacks.get(i);
+ if (cb == null) {
+ continue;
+ }
+ if (objectAnimator.shouldAutoCancel(cb)) {
+ ((Animator) animationCallbacks.get(i)).cancel();
+ }
+ }
+ }
+ }
+
+ final TestAnimationHandler mTestHandler;
+ final long mStartTime;
+ private long mTotalTimeDelta = 0;
+ private final Object mLock = new Object();
+
+ public AnimatorTestRule2() {
+ mStartTime = SystemClock.uptimeMillis();
+ mTestHandler = new TestAnimationHandler();
+ }
+
+ @NonNull
+ @Override
+ public Statement apply(@NonNull final Statement base, @NonNull Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ AnimationHandler.setTestHandler(mTestHandler);
+ try {
+ base.evaluate();
+ } finally {
+ AnimationHandler.setTestHandler(null);
+ }
+ }
+ };
+ }
+
+ /**
+ * Advances the animation clock by the given amount of delta in milliseconds. This call will
+ * produce an animation frame to all the ongoing animations. This method needs to be
+ * called on the same thread as {@link Animator#start()}.
+ *
+ * @param timeDelta the amount of milliseconds to advance
+ */
+ public void advanceTimeBy(long timeDelta) {
+ if (Looper.myLooper() == null) {
+ // Throw an exception
+ throw new AndroidRuntimeException("AnimationTestRule#advanceTimeBy(long) may only be"
+ + "called on Looper threads");
+ }
+ synchronized (mLock) {
+ // Advance time & pulse a frame
+ mTotalTimeDelta += timeDelta < 0 ? 0 : timeDelta;
+ }
+ // produce a frame
+ mTestHandler.onAnimationFrame(getCurrentTime());
+ }
+
+
+ /**
+ * Returns the current time in milliseconds tracked by AnimationHandler. Note that this is a
+ * different time than the time tracked by {@link SystemClock} This method needs to be called on
+ * the same thread as {@link Animator#start()}.
+ */
+ public long getCurrentTime() {
+ if (Looper.myLooper() == null) {
+ // Throw an exception
+ throw new AndroidRuntimeException("AnimationTestRule#getCurrentTime() may only be"
+ + "called on Looper threads");
+ }
+ synchronized (mLock) {
+ return mStartTime + mTotalTimeDelta;
+ }
+ }
+
+
+ private class TestProvider implements AnimationHandler.AnimationFrameCallbackProvider {
+ TestProvider() {
+ }
+
+ @Override
+ public void onNewCallbackAdded(AnimationHandler.AnimationFrameCallback callback) {
+ callback.doAnimationFrame(getCurrentTime());
+ }
+
+ @Override
+ public void postFrameCallback() {
+ }
+
+ @Override
+ public void setFrameDelay(long delay) {
+ }
+
+ @Override
+ public long getFrameDelay() {
+ return 0;
+ }
+ }
+}
+
diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
new file mode 100644
index 000000000000..bddd60b5970a
--- /dev/null
+++ b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.core.animation
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.doOnEnd
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+class AnimatorTestRuleTest : SysuiTestCase() {
+
+ @get:Rule val animatorTestRule = AnimatorTestRule2()
+
+ @Test
+ fun testA() {
+ didTouchA = false
+ didTouchB = false
+ ObjectAnimator.ofFloat(0f, 1f).apply {
+ duration = 100
+ doOnEnd { didTouchA = true }
+ start()
+ }
+ ObjectAnimator.ofFloat(0f, 1f).apply {
+ duration = 150
+ doOnEnd { didTouchA = true }
+ start()
+ }
+ animatorTestRule.advanceTimeBy(100)
+ assertThat(didTouchA).isTrue()
+ assertThat(didTouchB).isFalse()
+ }
+
+ @Test
+ fun testB() {
+ didTouchA = false
+ didTouchB = false
+ ObjectAnimator.ofFloat(0f, 1f).apply {
+ duration = 100
+ doOnEnd { didTouchB = true }
+ start()
+ }
+ ObjectAnimator.ofFloat(0f, 1f).apply {
+ duration = 150
+ doOnEnd { didTouchB = true }
+ start()
+ }
+ animatorTestRule.advanceTimeBy(100)
+ assertThat(didTouchA).isFalse()
+ assertThat(didTouchB).isTrue()
+ }
+
+ companion object {
+ var didTouchA = false
+ var didTouchB = false
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index b73330fb09c8..65f8610cfd43 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -393,6 +393,45 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
}
@Test
+ public void showNextSecurityScreenOrFinish_DeviceNotSecure() {
+ // GIVEN the current security method is SimPin
+ when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false);
+ mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin);
+
+ // WHEN a request is made from the SimPin screens to show the next security method
+ when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None);
+ mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
+ /* authenticated= */true,
+ TARGET_USER_ID,
+ /* bypassSecondaryLockScreen= */true,
+ SecurityMode.SimPin);
+
+ // THEN the next security method of None will dismiss keyguard.
+ verify(mViewMediatorCallback).keyguardDone(anyBoolean(), anyInt());
+ }
+
+ @Test
+ public void showNextSecurityScreenOrFinish_DeviceNotSecure_prevent_bypass_on() {
+ when(mFeatureFlags.isEnabled(Flags.PREVENT_BYPASS_KEYGUARD)).thenReturn(true);
+ // GIVEN the current security method is SimPin
+ when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false);
+ mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin);
+
+ // WHEN a request is made from the SimPin screens to show the next security method
+ when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None);
+ mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
+ /* authenticated= */true,
+ TARGET_USER_ID,
+ /* bypassSecondaryLockScreen= */true,
+ SecurityMode.SimPin);
+
+ // THEN the next security method of None will dismiss keyguard.
+ verify(mViewMediatorCallback).keyguardDone(anyBoolean(), anyInt());
+ }
+
+ @Test
public void showNextSecurityScreenOrFinish_ignoresCallWhenSecurityMethodHasChanged() {
//GIVEN current security mode has been set to PIN
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.PIN);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
index eb8295653199..353a7c370ab6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
@@ -26,6 +26,8 @@ import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SystemSettings
@@ -34,6 +36,10 @@ import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
private const val ON: Int = 1
@@ -53,6 +59,9 @@ class FontScalingDialogTest : SysuiTestCase() {
.getResources()
.getStringArray(com.android.settingslib.R.array.entryvalues_font_size)
+ @Captor
+ private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener>
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -61,7 +70,7 @@ class FontScalingDialogTest : SysuiTestCase() {
secureSettings = FakeSettings()
backgroundExecutor = FakeExecutor(FakeSystemClock())
fontScalingDialog =
- FontScalingDialog(mContext, systemSettings, secureSettings, backgroundExecutor)
+ spy(FontScalingDialog(mContext, systemSettings, secureSettings, backgroundExecutor))
}
@Test
@@ -70,7 +79,7 @@ class FontScalingDialogTest : SysuiTestCase() {
val seekBar: SeekBar = fontScalingDialog.findViewById<SeekBar>(R.id.seekbar)!!
val progress: Int = seekBar.getProgress()
- val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f)
+ val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
assertThat(currentScale).isEqualTo(fontSizeValueArray[progress].toFloat())
@@ -91,7 +100,7 @@ class FontScalingDialogTest : SysuiTestCase() {
iconEndFrame.performClick()
backgroundExecutor.runAllReady()
- val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f)
+ val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
assertThat(seekBar.getProgress()).isEqualTo(1)
assertThat(currentScale).isEqualTo(fontSizeValueArray[1].toFloat())
@@ -112,7 +121,7 @@ class FontScalingDialogTest : SysuiTestCase() {
iconStartFrame.performClick()
backgroundExecutor.runAllReady()
- val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f)
+ val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
assertThat(seekBar.getProgress()).isEqualTo(fontSizeValueArray.size - 2)
assertThat(currentScale)
.isEqualTo(fontSizeValueArray[fontSizeValueArray.size - 2].toFloat())
@@ -141,4 +150,64 @@ class FontScalingDialogTest : SysuiTestCase() {
fontScalingDialog.dismiss()
}
+
+ @Test
+ fun dragSeekbar_systemFontSizeSettingsDoesNotChange() {
+ val slider: SeekBarWithIconButtonsView = spy(SeekBarWithIconButtonsView(mContext))
+ whenever(
+ fontScalingDialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider)
+ )
+ .thenReturn(slider)
+ fontScalingDialog.show()
+ verify(slider).setOnSeekBarChangeListener(capture(seekBarChangeCaptor))
+ val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!!
+
+ // Default seekbar progress for font size is 1, simulate dragging to 0 without
+ // releasing the finger.
+ seekBarChangeCaptor.value.onStartTrackingTouch(seekBar)
+ // Update seekbar progress. This will trigger onProgressChanged in the
+ // OnSeekBarChangeListener and the seekbar could get updated progress value
+ // in onStopTrackingTouch.
+ seekBar.progress = 0
+ backgroundExecutor.runAllReady()
+
+ // Verify that the scale of font size remains the default value 1.0f.
+ var systemScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
+ assertThat(systemScale).isEqualTo(1.0f)
+
+ // Simulate releasing the finger from the seekbar.
+ seekBarChangeCaptor.value.onStopTrackingTouch(seekBar)
+ backgroundExecutor.runAllReady()
+
+ // Verify that the scale of font size has been updated.
+ systemScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
+ assertThat(systemScale).isEqualTo(fontSizeValueArray[0].toFloat())
+
+ fontScalingDialog.dismiss()
+ }
+
+ @Test
+ fun dragSeekBar_createTextPreview() {
+ val slider: SeekBarWithIconButtonsView = spy(SeekBarWithIconButtonsView(mContext))
+ whenever(
+ fontScalingDialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider)
+ )
+ .thenReturn(slider)
+ fontScalingDialog.show()
+ verify(slider).setOnSeekBarChangeListener(capture(seekBarChangeCaptor))
+ val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!!
+
+ // Default seekbar progress for font size is 1, simulate dragging to 0 without
+ // releasing the finger
+ seekBarChangeCaptor.value.onStartTrackingTouch(seekBar)
+ seekBarChangeCaptor.value.onProgressChanged(
+ seekBar,
+ /* progress= */ 0,
+ /* fromUser= */ false
+ )
+ backgroundExecutor.runAllReady()
+
+ verify(fontScalingDialog).createTextPreview(/* index= */ 0)
+ fontScalingDialog.dismiss()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index 6ddba0b4719c..b41053cdea50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -25,7 +25,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.timeout
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.junit.MockitoJUnit
@@ -49,17 +49,31 @@ class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() {
}
@Test
- fun testEnableDetector_shouldPostRunnable() {
+ fun testEnableDetector_expandWithTrack_shouldPostRunnable() {
detector.enable(action)
// simulate notification expand
shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, true, 5566f)
- verify(action, timeout(5000).times(1)).run()
+ verify(action).run()
+ }
+
+ @Test
+ fun testEnableDetector_trackOnly_shouldPostRunnable() {
+ detector.enable(action)
+ // simulate notification expand
+ shadeExpansionStateManager.onPanelExpansionChanged(5566f, false, true, 5566f)
+ verify(action).run()
+ }
+
+ @Test
+ fun testEnableDetector_expandOnly_shouldPostRunnable() {
+ detector.enable(action)
+ // simulate notification expand
+ shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, false, 5566f)
+ verify(action).run()
}
@Test
fun testEnableDetector_shouldNotPostRunnable() {
- var detector =
- AuthDialogPanelInteractionDetector(shadeExpansionStateManager, mContext.mainExecutor)
detector.enable(action)
detector.disable()
shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, true, 5566f)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 2a72e7d85d3c..18abfa546ea6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -26,8 +26,10 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.res.Resources;
+import android.graphics.Region;
import android.os.Handler;
import android.testing.AndroidTestingRunner;
+import android.view.AttachedSurfaceControl;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
@@ -76,6 +78,9 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
ComplicationHostViewController mComplicationHostViewController;
@Mock
+ AttachedSurfaceControl mAttachedSurfaceControl;
+
+ @Mock
ViewGroup mDreamOverlayContentView;
@Mock
@@ -108,6 +113,8 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
when(mDreamOverlayContainerView.getResources()).thenReturn(mResources);
when(mDreamOverlayContainerView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
when(mDreamOverlayContainerView.getViewRootImpl()).thenReturn(mViewRoot);
+ when(mDreamOverlayContainerView.getRootSurfaceControl())
+ .thenReturn(mAttachedSurfaceControl);
mController = new DreamOverlayContainerViewController(
mDreamOverlayContainerView,
@@ -128,6 +135,12 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
}
@Test
+ public void testRootSurfaceControlInsetSetOnAttach() {
+ mController.onViewAttached();
+ verify(mAttachedSurfaceControl).setTouchableRegion(eq(Region.obtain()));
+ }
+
+ @Test
public void testDreamOverlayStatusBarViewControllerInitialized() {
mController.init();
verify(mDreamOverlayStatusBarViewController).init();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
new file mode 100644
index 000000000000..ccd631ec37d0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
@@ -0,0 +1,346 @@
+package com.android.systemui.graphics
+
+import android.content.res.Resources
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.ImageDecoder
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.graphics.drawable.VectorDrawable
+import android.net.Uri
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+@RunWith(AndroidJUnit4::class)
+class ImageLoaderTest : SysuiTestCase() {
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+ private val imageLoader = ImageLoader(context, testDispatcher)
+
+ private lateinit var imgFile: File
+
+ @Before
+ fun setUp() {
+ val context = context.createPackageContext("com.android.systemui.tests", 0)
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ com.android.systemui.tests.R.drawable.romainguy_rockaway
+ )
+
+ imgFile = File.createTempFile("image", ".png", context.cacheDir)
+ imgFile.deleteOnExit()
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, FileOutputStream(imgFile))
+ }
+
+ @After
+ fun tearDown() {
+ imgFile.delete()
+ }
+
+ @Test
+ fun invalidResource_drawable_returnsNull() =
+ testScope.runTest { assertThat(imageLoader.loadDrawable(ImageLoader.Res(-1))).isNull() }
+
+ @Test
+ fun invalidResource_bitmap_returnsNull() =
+ testScope.runTest { assertThat(imageLoader.loadBitmap(ImageLoader.Res(-1))).isNull() }
+
+ @Test
+ fun invalidUri_returnsNull() =
+ testScope.runTest {
+ assertThat(imageLoader.loadBitmap(ImageLoader.Uri("this.is/bogus"))).isNull()
+ }
+
+ @Test
+ fun invalidFile_returnsNull() =
+ testScope.runTest {
+ assertThat(imageLoader.loadBitmap(ImageLoader.File("this is broken!"))).isNull()
+ }
+
+ @Test
+ fun invalidIcon_returnsNull() =
+ testScope.runTest {
+ assertThat(imageLoader.loadDrawable(Icon.createWithFilePath("this is broken"))).isNull()
+ }
+
+ @Test
+ fun invalidIS_returnsNull() =
+ testScope.runTest {
+ assertThat(
+ imageLoader.loadDrawable(
+ ImageLoader.InputStream(ByteArrayInputStream(ByteArray(0)))
+ )
+ )
+ .isNull()
+ }
+
+ @Test
+ fun validBitmapResource_loadDrawable_returnsBitmapDrawable() =
+ testScope.runTest {
+ val context = context.createPackageContext("com.android.systemui.tests", 0)
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ com.android.systemui.tests.R.drawable.romainguy_rockaway
+ )
+ assertThat(bitmap).isNotNull()
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ ImageLoader.Res(
+ com.android.systemui.tests.R.drawable.romainguy_rockaway,
+ context
+ )
+ )
+ assertBitmapEqualToDrawable(loadedDrawable, bitmap)
+ }
+
+ @Test
+ fun validBitmapResource_loadBitmap_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ R.drawable.dessert_zombiegingerbread
+ )
+ val loadedBitmap =
+ imageLoader.loadBitmap(ImageLoader.Res(R.drawable.dessert_zombiegingerbread))
+ assertBitmapEqualToBitmap(loadedBitmap, bitmap)
+ }
+
+ @Test
+ fun validBitmapUri_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ R.drawable.dessert_zombiegingerbread
+ )
+
+ val uri =
+ "android.resource://${context.packageName}/${R.drawable.dessert_zombiegingerbread}"
+ val loadedBitmap = imageLoader.loadBitmap(ImageLoader.Uri(uri))
+ assertBitmapEqualToBitmap(loadedBitmap, bitmap)
+ }
+
+ @Test
+ fun validBitmapFile_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap = BitmapFactory.decodeFile(imgFile.absolutePath)
+ val loadedBitmap = imageLoader.loadBitmap(ImageLoader.File(imgFile))
+ assertBitmapEqualToBitmap(loadedBitmap, bitmap)
+ }
+
+ @Test
+ fun validInputStream_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap = BitmapFactory.decodeFile(imgFile.absolutePath)
+ val loadedBitmap =
+ imageLoader.loadBitmap(ImageLoader.InputStream(FileInputStream(imgFile)))
+ assertBitmapEqualToBitmap(loadedBitmap, bitmap)
+ }
+
+ @Test
+ fun validBitmapIcon_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ R.drawable.dessert_zombiegingerbread
+ )
+ val loadedDrawable = imageLoader.loadDrawable(Icon.createWithBitmap(bitmap))
+ assertBitmapEqualToDrawable(loadedDrawable, bitmap)
+ }
+
+ @Test
+ fun validUriIcon_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ R.drawable.dessert_zombiegingerbread
+ )
+ val uri =
+ "android.resource://${context.packageName}/${R.drawable.dessert_zombiegingerbread}"
+ val loadedDrawable = imageLoader.loadDrawable(Icon.createWithContentUri(Uri.parse(uri)))
+ assertBitmapEqualToDrawable(loadedDrawable, bitmap)
+ }
+
+ @Test
+ fun validDataIcon_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ R.drawable.dessert_zombiegingerbread
+ )
+ val bos =
+ ByteArrayOutputStream(
+ bitmap.byteCount * 2
+ ) // Compressed bitmap should be smaller than its source.
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos)
+
+ val array = bos.toByteArray()
+ val loadedDrawable = imageLoader.loadDrawable(Icon.createWithData(array, 0, array.size))
+ assertBitmapEqualToDrawable(loadedDrawable, bitmap)
+ }
+
+ @Test
+ fun validSystemResourceIcon_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap =
+ Resources.getSystem().getDrawable(android.R.drawable.ic_dialog_alert, context.theme)
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ Icon.createWithResource("android", android.R.drawable.ic_dialog_alert)
+ )
+ assertBitmapEqualToDrawable(loadedDrawable, (bitmap as BitmapDrawable).bitmap)
+ }
+
+ @Test
+ fun invalidDifferentPackageResourceIcon_returnsNull() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ Icon.createWithResource(
+ "noooope.wrong.package",
+ R.drawable.dessert_zombiegingerbread
+ )
+ )
+ assertThat(loadedDrawable).isNull()
+ }
+
+ @Test
+ fun validBitmapResource_widthMoreRestricted_downsizesKeepingAspectRatio() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(ImageLoader.File(imgFile), maxWidth = 160, maxHeight = 160)
+ val loadedBitmap = assertBitmapInDrawable(loadedDrawable)
+ assertThat(loadedBitmap.width).isEqualTo(160)
+ assertThat(loadedBitmap.height).isEqualTo(106)
+ }
+
+ @Test
+ fun validBitmapResource_heightMoreRestricted_downsizesKeepingAspectRatio() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(ImageLoader.File(imgFile), maxWidth = 160, maxHeight = 50)
+ val loadedBitmap = assertBitmapInDrawable(loadedDrawable)
+ assertThat(loadedBitmap.width).isEqualTo(74)
+ assertThat(loadedBitmap.height).isEqualTo(50)
+ }
+
+ @Test
+ fun validBitmapResource_onlyWidthRestricted_downsizesKeepingAspectRatio() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ ImageLoader.File(imgFile),
+ maxWidth = 160,
+ maxHeight = ImageLoader.DO_NOT_RESIZE
+ )
+ val loadedBitmap = assertBitmapInDrawable(loadedDrawable)
+ assertThat(loadedBitmap.width).isEqualTo(160)
+ assertThat(loadedBitmap.height).isEqualTo(106)
+ }
+
+ @Test
+ fun validBitmapResource_onlyHeightRestricted_downsizesKeepingAspectRatio() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ ImageLoader.Res(R.drawable.bubble_thumbnail),
+ maxWidth = ImageLoader.DO_NOT_RESIZE,
+ maxHeight = 120
+ )
+ val loadedBitmap = assertBitmapInDrawable(loadedDrawable)
+ assertThat(loadedBitmap.width).isEqualTo(123)
+ assertThat(loadedBitmap.height).isEqualTo(120)
+ }
+
+ @Test
+ fun validVectorDrawable_loadDrawable_successfullyLoaded() =
+ testScope.runTest {
+ val loadedDrawable = imageLoader.loadDrawable(ImageLoader.Res(R.drawable.ic_settings))
+ assertThat(loadedDrawable).isNotNull()
+ assertThat(loadedDrawable).isInstanceOf(VectorDrawable::class.java)
+ }
+
+ @Test
+ fun validVectorDrawable_loadBitmap_returnsNull() =
+ testScope.runTest {
+ val loadedBitmap = imageLoader.loadBitmap(ImageLoader.Res(R.drawable.ic_settings))
+ assertThat(loadedBitmap).isNull()
+ }
+
+ @Test
+ fun validVectorDrawableIcon_loadDrawable_successfullyLoaded() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(Icon.createWithResource(context, R.drawable.ic_settings))
+ assertThat(loadedDrawable).isNotNull()
+ assertThat(loadedDrawable).isInstanceOf(VectorDrawable::class.java)
+ }
+
+ @Test
+ fun hardwareAllocator_returnsHardwareBitmap() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ ImageLoader.File(imgFile),
+ allocator = ImageDecoder.ALLOCATOR_HARDWARE
+ )
+ assertThat(loadedDrawable).isNotNull()
+ assertThat((loadedDrawable as BitmapDrawable).bitmap.config)
+ .isEqualTo(Bitmap.Config.HARDWARE)
+ }
+
+ @Test
+ fun softwareAllocator_returnsSoftwareBitmap() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ ImageLoader.File(imgFile),
+ allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+ )
+ assertThat(loadedDrawable).isNotNull()
+ assertThat((loadedDrawable as BitmapDrawable).bitmap.config)
+ .isNotEqualTo(Bitmap.Config.HARDWARE)
+ }
+
+ private fun assertBitmapInDrawable(drawable: Drawable?): Bitmap {
+ assertThat(drawable).isNotNull()
+ assertThat(drawable).isInstanceOf(BitmapDrawable::class.java)
+ return (drawable as BitmapDrawable).bitmap
+ }
+
+ private fun assertBitmapEqualToDrawable(actual: Drawable?, expected: Bitmap) {
+ val actualBitmap = assertBitmapInDrawable(actual)
+ assertBitmapEqualToBitmap(actualBitmap, expected)
+ }
+
+ private fun assertBitmapEqualToBitmap(actual: Bitmap?, expected: Bitmap) {
+ assertThat(actual).isNotNull()
+ assertThat(actual?.width).isEqualTo(expected.width)
+ assertThat(actual?.height).isEqualTo(expected.height)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 7e052bfa15d2..fb9336734d99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -54,6 +54,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -95,6 +96,8 @@ public class TileServicesTest extends SysuiTestCase {
private QSHost mQSHost;
@Mock
private PanelInteractor mPanelInteractor;
+ @Captor
+ private ArgumentCaptor<CommandQueue.Callbacks> mCallbacksArgumentCaptor;
@Before
public void setUp() throws Exception {
@@ -251,6 +254,41 @@ public class TileServicesTest extends SysuiTestCase {
verify(mPanelInteractor).forceCollapsePanels();
}
+ @Test
+ public void tileFreedForCorrectUser() throws RemoteException {
+ verify(mCommandQueue).addCallback(mCallbacksArgumentCaptor.capture());
+
+ ComponentName componentName = new ComponentName("pkg", "cls");
+ CustomTile tileUser0 = mock(CustomTile.class);
+ CustomTile tileUser1 = mock(CustomTile.class);
+
+ when(tileUser0.getComponent()).thenReturn(componentName);
+ when(tileUser1.getComponent()).thenReturn(componentName);
+ when(tileUser0.getUser()).thenReturn(0);
+ when(tileUser1.getUser()).thenReturn(1);
+
+ // Create a tile for user 0
+ TileServiceManager manager0 = mTileService.getTileWrapper(tileUser0);
+ when(manager0.isActiveTile()).thenReturn(true);
+ // Then create a tile for user 1
+ TileServiceManager manager1 = mTileService.getTileWrapper(tileUser1);
+ when(manager1.isActiveTile()).thenReturn(true);
+
+ // When the tile for user 0 gets freed
+ mTileService.freeService(tileUser0, manager0);
+ // and the user is 1
+ when(mUserTracker.getUserId()).thenReturn(1);
+
+ // a call to requestListeningState
+ mCallbacksArgumentCaptor.getValue().requestTileServiceListeningState(componentName);
+ mTestableLooper.processAllMessages();
+
+ // will call in the correct tile
+ verify(manager1).setBindRequested(true);
+ // and set it to listening
+ verify(manager1.getTileService()).onStartListening();
+ }
+
private class TestTileServices extends TileServices {
TestTileServices(QSHost host, Provider<Handler> handlerProvider,
BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
@@ -268,6 +306,8 @@ public class TileServicesTest extends SysuiTestCase {
when(manager.isLifecycleStarted()).thenReturn(true);
Binder b = new Binder();
when(manager.getToken()).thenReturn(b);
+ IQSTileService service = mock(IQSTileService.class);
+ when(manager.getTileService()).thenReturn(service);
return manager;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 7087c0135998..1bd13aadc1f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -162,6 +162,8 @@ import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.time.SystemClock;
import com.android.wm.shell.animation.FlingAnimationUtils;
+import dagger.Lazy;
+
import org.junit.After;
import org.junit.Before;
import org.mockito.ArgumentCaptor;
@@ -173,7 +175,6 @@ import org.mockito.stubbing.Answer;
import java.util.List;
import java.util.Optional;
-import dagger.Lazy;
import kotlinx.coroutines.CoroutineDispatcher;
public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@@ -207,7 +208,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@Mock protected KeyguardStateController mKeyguardStateController;
@Mock protected DozeLog mDozeLog;
@Mock protected ShadeLogger mShadeLog;
- @Mock protected ShadeHeightLogger mShadeHeightLogger;
@Mock protected CommandQueue mCommandQueue;
@Mock protected VibratorHelper mVibratorHelper;
@Mock protected LatencyTracker mLatencyTracker;
@@ -519,7 +519,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
mMetricsLogger,
mShadeLog,
- mShadeHeightLogger,
mConfigurationController,
() -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
mConversationNotificationManager, mMediaHierarchyManager,
@@ -578,7 +577,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mCentralSurfaces,
null,
() -> {},
- mNotificationShelfController);
+ mNotificationShelfController,
+ mHeadsUpManager);
mNotificationPanelViewController.setTrackingStartedListener(() -> {});
mNotificationPanelViewController.setOpenCloseListener(
new NotificationPanelViewController.OpenCloseListener() {
@@ -588,7 +588,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@Override
public void onOpenStarted() {}
});
- mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor =
ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
verify(mView, atLeast(1)).addOnAttachStateChangeListener(
@@ -601,7 +600,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mAccessibilityDelegate = accessibilityDelegateArgumentCaptor.getValue();
mNotificationPanelViewController.getStatusBarStateController()
.addCallback(mNotificationPanelViewController.getStatusBarStateListener());
- mNotificationPanelViewController
+ mNotificationPanelViewController.getShadeHeadsUpTracker()
.setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class));
verify(mNotificationStackScrollLayoutController)
.setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index d36cc7e0ddbe..2db9c9788bf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -456,6 +456,34 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
}
@Test
+ public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenNot() {
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+ mStatusBarStateController.setState(KEYGUARD);
+ enableSplitShade(/* enabled= */ true);
+
+ mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(true);
+ setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
+ assertKeyguardStatusViewCentered();
+
+ mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(false);
+ assertKeyguardStatusViewNotCentered();
+ }
+
+ @Test
+ public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenStillCenteredIfNoNotifs() {
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
+ mStatusBarStateController.setState(KEYGUARD);
+ enableSplitShade(/* enabled= */ true);
+
+ mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(true);
+ setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
+ assertKeyguardStatusViewCentered();
+
+ mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(false);
+ assertKeyguardStatusViewCentered();
+ }
+
+ @Test
public void testCanCollapsePanelOnTouch_trueForKeyGuard() {
mStatusBarStateController.setState(KEYGUARD);
@@ -702,7 +730,8 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
// Start fold animation & Capture Listeners
- mNotificationPanelViewController.startFoldToAodAnimation(() -> {}, () -> {}, () -> {});
+ mNotificationPanelViewController.getShadeFoldAnimator()
+ .startFoldToAodAnimation(() -> {}, () -> {}, () -> {});
verify(mViewPropertyAnimator).setListener(animCaptor.capture());
verify(mViewPropertyAnimator).setUpdateListener(updateCaptor.capture());
@@ -717,7 +746,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
enableSplitShade(/* enabled= */ true);
mStatusBarStateController.setState(KEYGUARD);
- mNotificationPanelViewController.expandWithQs();
+ mNotificationPanelViewController.expandToQs();
verify(mLockscreenShadeTransitionController).goToLockedShade(
/* expandedView= */null, /* needsQSAnimation= */true);
@@ -798,7 +827,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
@Test
public void testQsExpansionChangedToDefaultWhenRotatingFromOrToSplitShade() {
// to make sure shade is in expanded state
- mNotificationPanelViewController.startWaitingForOpenPanelGesture();
+ mNotificationPanelViewController.startWaitingForExpandGesture();
// switch to split shade from portrait (default state)
enableSplitShade(/* enabled= */ true);
@@ -817,7 +846,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
mNotificationPanelViewController.setExpandedFraction(1f);
assertThat(mNotificationPanelViewController.isClosing()).isFalse();
- mNotificationPanelViewController.animateCloseQs(false);
+ mNotificationPanelViewController.animateCollapseQs(false);
assertThat(mNotificationPanelViewController.isClosing()).isTrue();
}
@@ -825,7 +854,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
@Test
public void getMaxPanelTransitionDistance_expanding_inSplitShade_returnsSplitShadeFullTransitionDistance() {
enableSplitShade(true);
- mNotificationPanelViewController.expandWithQs();
+ mNotificationPanelViewController.expandToQs();
int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
@@ -835,7 +864,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
@Test
public void getMaxPanelTransitionDistance_inSplitShade_withHeadsUp_returnsBiggerValue() {
enableSplitShade(true);
- mNotificationPanelViewController.expandWithQs();
+ mNotificationPanelViewController.expandToQs();
when(mHeadsUpManager.isTrackingHeadsUp()).thenReturn(true);
when(mQsController.calculatePanelHeightExpanded(anyInt())).thenReturn(10000);
mNotificationPanelViewController.setHeadsUpDraggingStartingHeight(
@@ -852,7 +881,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
public void getMaxPanelTransitionDistance_expandingSplitShade_keyguard_returnsNonSplitShadeValue() {
mStatusBarStateController.setState(KEYGUARD);
enableSplitShade(true);
- mNotificationPanelViewController.expandWithQs();
+ mNotificationPanelViewController.expandToQs();
int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
@@ -862,7 +891,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
@Test
public void getMaxPanelTransitionDistance_expanding_notSplitShade_returnsNonSplitShadeValue() {
enableSplitShade(false);
- mNotificationPanelViewController.expandWithQs();
+ mNotificationPanelViewController.expandToQs();
int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
@@ -1033,11 +1062,11 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
mStatusBarStateController.setState(SHADE);
mNotificationPanelViewController.setExpandedHeight(0);
- assertThat(mNotificationPanelViewController.isShadeFullyOpen()).isFalse();
+ assertThat(mNotificationPanelViewController.isShadeFullyExpanded()).isFalse();
int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
mNotificationPanelViewController.setExpandedHeight(transitionDistance);
- assertThat(mNotificationPanelViewController.isShadeFullyOpen()).isTrue();
+ assertThat(mNotificationPanelViewController.isShadeFullyExpanded()).isTrue();
}
@Test
@@ -1046,12 +1075,12 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
mNotificationPanelViewController.setExpandedHeight(transitionDistance);
- assertThat(mNotificationPanelViewController.isShadeFullyOpen()).isFalse();
+ assertThat(mNotificationPanelViewController.isShadeFullyExpanded()).isFalse();
}
@Test
public void shadeExpanded_onShadeLocked() {
mStatusBarStateController.setState(SHADE_LOCKED);
- assertThat(mNotificationPanelViewController.isShadeFullyOpen()).isTrue();
+ assertThat(mNotificationPanelViewController.isShadeFullyExpanded()).isTrue();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index ab615f99ebad..932a1f9ca587 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -223,7 +223,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
fun testGoToLockedShadeCreatesQSAnimation() {
transitionController.goToLockedShade(null)
verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
- verify(notificationPanelController).animateToFullShade(anyLong())
+ verify(notificationPanelController).transitionToExpandedShade(anyLong())
assertNotNull(transitionController.dragDownAnimator)
}
@@ -231,7 +231,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
fun testGoToLockedShadeDoesntCreateQSAnimation() {
transitionController.goToLockedShade(null, needsQSAnimation = false)
verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
- verify(notificationPanelController).animateToFullShade(anyLong())
+ verify(notificationPanelController).transitionToExpandedShade(anyLong())
assertNull(transitionController.dragDownAnimator)
}
@@ -239,7 +239,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
fun testGoToLockedShadeAlwaysCreatesQSAnimationInSplitShade() {
enableSplitShade()
transitionController.goToLockedShade(null, needsQSAnimation = true)
- verify(notificationPanelController).animateToFullShade(anyLong())
+ verify(notificationPanelController).transitionToExpandedShade(anyLong())
assertNotNull(transitionController.dragDownAnimator)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 08a9f3139d71..7b59cc284181 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -22,7 +22,7 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.View
import android.widget.FrameLayout
-import androidx.core.animation.AnimatorTestRule
+import androidx.core.animation.AnimatorTestRule2
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
@@ -70,7 +70,7 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler
private val fakeFeatureFlags = FakeFeatureFlags()
- @get:Rule val animatorTestRule = AnimatorTestRule()
+ @get:Rule val animatorTestRule = AnimatorTestRule2()
@Before
fun setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
index 7a6779684fc5..bef9fcb5697c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
@@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.plugins.log.LogcatEchoTracker
@@ -45,47 +45,130 @@ class NotificationWakeUpCoordinatorLoggerTest : SysuiTestCase() {
}
@Test
- fun setDozeAmountWillThrottleFractionalUpdates() {
- logger.logSetDozeAmount(0f, 0f, "source1", StatusBarState.SHADE, changed = false)
+ fun updateVisibilityThrottleFractionalUpdates() {
+ logger.logSetVisibilityAmount(0f)
verifyDidLog(1)
- logger.logSetDozeAmount(0.1f, 0.1f, "source1", StatusBarState.SHADE, changed = true)
+ logger.logSetVisibilityAmount(0.1f)
verifyDidLog(1)
- logger.logSetDozeAmount(0.2f, 0.2f, "source1", StatusBarState.SHADE, changed = true)
- logger.logSetDozeAmount(0.3f, 0.3f, "source1", StatusBarState.SHADE, changed = true)
- logger.logSetDozeAmount(0.4f, 0.4f, "source1", StatusBarState.SHADE, changed = true)
- logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.SHADE, changed = true)
+ logger.logSetVisibilityAmount(0.2f)
+ logger.logSetVisibilityAmount(0.3f)
+ logger.logSetVisibilityAmount(0.4f)
+ logger.logSetVisibilityAmount(0.5f)
verifyDidLog(0)
- logger.logSetDozeAmount(1f, 1f, "source1", StatusBarState.SHADE, changed = true)
+ logger.logSetVisibilityAmount(1f)
verifyDidLog(1)
}
@Test
- fun setDozeAmountWillIncludeFractionalUpdatesWhenStateChanges() {
- logger.logSetDozeAmount(0f, 0f, "source1", StatusBarState.SHADE, changed = false)
+ fun updateHideAmountThrottleFractionalOrRepeatedUpdates() {
+ logger.logSetHideAmount(0f)
verifyDidLog(1)
- logger.logSetDozeAmount(0.1f, 0.1f, "source1", StatusBarState.SHADE, changed = true)
+ logger.logSetHideAmount(0f)
+ logger.logSetHideAmount(0f)
+ verifyDidLog(0)
+ logger.logSetHideAmount(0.1f)
+ verifyDidLog(1)
+ logger.logSetHideAmount(0.2f)
+ logger.logSetHideAmount(0.3f)
+ logger.logSetHideAmount(0.4f)
+ logger.logSetHideAmount(0.5f)
+ logger.logSetHideAmount(0.5f)
+ logger.logSetHideAmount(0.5f)
+ verifyDidLog(0)
+ logger.logSetHideAmount(1f)
+ verifyDidLog(1)
+ logger.logSetHideAmount(1f)
+ logger.logSetHideAmount(1f)
+ verifyDidLog(0)
+ }
+
+ @Test
+ fun updateDozeAmountWillThrottleFractionalInputUpdates() {
+ logger.logUpdateDozeAmount(0f, 0f, null, 0f, StatusBarState.SHADE, changed = false)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0.1f, 0f, null, 0.1f, StatusBarState.SHADE, changed = true)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0.2f, 0f, null, 0.2f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.3f, 0f, null, 0.3f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.4f, 0f, null, 0.4f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.5f, 0f, null, 0.5f, StatusBarState.SHADE, changed = true)
+ verifyDidLog(0)
+ logger.logUpdateDozeAmount(1f, 0f, null, 1f, StatusBarState.SHADE, changed = true)
+ verifyDidLog(1)
+ }
+
+ @Test
+ fun updateDozeAmountWillThrottleFractionalDelayUpdates() {
+ logger.logUpdateDozeAmount(0f, 0f, null, 0f, StatusBarState.SHADE, changed = false)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0f, 0.1f, null, 0.1f, StatusBarState.SHADE, changed = true)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0f, 0.2f, null, 0.2f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0f, 0.3f, null, 0.3f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0f, 0.4f, null, 0.4f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0f, 0.5f, null, 0.5f, StatusBarState.SHADE, changed = true)
+ verifyDidLog(0)
+ logger.logUpdateDozeAmount(0f, 1f, null, 1f, StatusBarState.SHADE, changed = true)
verifyDidLog(1)
- logger.logSetDozeAmount(0.2f, 0.2f, "source1", StatusBarState.SHADE, changed = true)
- logger.logSetDozeAmount(0.3f, 0.3f, "source1", StatusBarState.SHADE, changed = true)
- logger.logSetDozeAmount(0.4f, 0.4f, "source1", StatusBarState.SHADE, changed = true)
- logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.SHADE, changed = true)
+ }
+
+ @Test
+ fun updateDozeAmountWillIncludeFractionalUpdatesWhenOtherInputChangesFractionality() {
+ logger.logUpdateDozeAmount(0.0f, 1.0f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0.1f, 1.0f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0.2f, 1.0f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ logger.logUpdateDozeAmount(0.3f, 1.0f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ logger.logUpdateDozeAmount(0.4f, 1.0f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ verifyDidLog(0)
+ logger.logUpdateDozeAmount(0.5f, 0.9f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0.6f, 0.8f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ logger.logUpdateDozeAmount(0.8f, 0.6f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ logger.logUpdateDozeAmount(0.9f, 0.5f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ verifyDidLog(0)
+ logger.logUpdateDozeAmount(1.0f, 0.4f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(1.0f, 0.3f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ logger.logUpdateDozeAmount(1.0f, 0.2f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ logger.logUpdateDozeAmount(1.0f, 0.1f, 1f, 1f, StatusBarState.SHADE, changed = false)
verifyDidLog(0)
- logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.KEYGUARD, changed = false)
+ logger.logUpdateDozeAmount(1.0f, 0.0f, 1f, 1f, StatusBarState.SHADE, changed = false)
verifyDidLog(1)
}
@Test
- fun setDozeAmountWillIncludeFractionalUpdatesWhenSourceChanges() {
- logger.logSetDozeAmount(0f, 0f, "source1", StatusBarState.SHADE, changed = false)
+ fun updateDozeAmountWillIncludeFractionalUpdatesWhenStateChanges() {
+ logger.logUpdateDozeAmount(0f, 0f, null, 0f, StatusBarState.SHADE, changed = false)
verifyDidLog(1)
- logger.logSetDozeAmount(0.1f, 0.1f, "source1", StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.1f, 0f, null, 0.1f, StatusBarState.SHADE, changed = true)
verifyDidLog(1)
- logger.logSetDozeAmount(0.2f, 0.2f, "source1", StatusBarState.SHADE, changed = true)
- logger.logSetDozeAmount(0.3f, 0.3f, "source1", StatusBarState.SHADE, changed = true)
- logger.logSetDozeAmount(0.4f, 0.4f, "source1", StatusBarState.SHADE, changed = true)
- logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.2f, 0f, null, 0.2f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.3f, 0f, null, 0.3f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.4f, 0f, null, 0.4f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.5f, 0f, null, 0.5f, StatusBarState.SHADE, changed = true)
verifyDidLog(0)
- logger.logSetDozeAmount(0.5f, 0.5f, "source2", StatusBarState.SHADE, changed = false)
+ logger.logUpdateDozeAmount(0.5f, 0f, null, 0.5f, StatusBarState.KEYGUARD, changed = false)
+ verifyDidLog(1)
+ }
+
+ @Test
+ fun updateDozeAmountWillIncludeFractionalUpdatesWhenHardOverrideChanges() {
+ logger.logUpdateDozeAmount(0f, 0f, null, 0f, StatusBarState.SHADE, changed = false)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0.1f, 0f, null, 0.1f, StatusBarState.SHADE, changed = true)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0.2f, 0f, null, 0.2f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.3f, 0f, null, 0.3f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.4f, 0f, null, 0.4f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.5f, 0f, null, 0.5f, StatusBarState.SHADE, changed = true)
+ verifyDidLog(0)
+ logger.logUpdateDozeAmount(0.5f, 0f, 1f, 1f, StatusBarState.SHADE, changed = true)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0.5f, 0f, 0f, 0f, StatusBarState.SHADE, changed = true)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0.5f, 0f, null, 0.5f, StatusBarState.SHADE, changed = true)
verifyDidLog(1)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
index 95591a4b321c..be3b7234a1a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
@@ -17,31 +17,42 @@
package com.android.systemui.statusbar.notification
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.core.animation.AnimatorTestRule2
import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.NotificationPanelViewController.WAKEUP_ANIMATION_DELAY_MS
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_WAKEUP
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.anyFloat
import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
@RunWith(AndroidTestingRunner::class)
@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
+ @get:Rule val animatorTestRule = AnimatorTestRule2()
+
private val dumpManager: DumpManager = mock()
private val headsUpManager: HeadsUpManager = mock()
private val statusBarStateController: StatusBarStateController = mock()
@@ -50,6 +61,7 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
private val screenOffAnimationController: ScreenOffAnimationController = mock()
private val logger: NotificationWakeUpCoordinatorLogger = mock()
private val stackScrollerController: NotificationStackScrollLayoutController = mock()
+ private val wakeUpListener: NotificationWakeUpCoordinator.WakeUpListener = mock()
private lateinit var notificationWakeUpCoordinator: NotificationWakeUpCoordinator
private lateinit var statusBarStateCallback: StatusBarStateController.StateListener
@@ -57,7 +69,8 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
private var bypassEnabled: Boolean = false
private var statusBarState: Int = StatusBarState.KEYGUARD
- private var dozeAmount: Float = 0f
+ private fun eased(dozeAmount: Float) =
+ notificationWakeUpCoordinator.dozeAmountInterpolator.getInterpolation(dozeAmount)
private fun setBypassEnabled(enabled: Boolean) {
bypassEnabled = enabled
@@ -70,7 +83,6 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
}
private fun setDozeAmount(dozeAmount: Float) {
- this.dozeAmount = dozeAmount
statusBarStateCallback.onDozeAmountChanged(dozeAmount, dozeAmount)
}
@@ -129,7 +141,7 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
fun setDozeToZeroWithBypassWillFullyHideNotifications() {
bypassEnabled = true
setDozeAmount(0f)
- verifyStackScrollerDozeAndHideAmount(dozeAmount = 01f, hideAmount = 1f)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
}
@@ -152,12 +164,161 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
assertThat(notificationWakeUpCoordinator.statusBarState).isEqualTo(StatusBarState.SHADE)
}
+ private val delayedDozeDelay = WAKEUP_ANIMATION_DELAY_MS.toLong()
+ private val delayedDozeDuration = ANIMATION_DURATION_WAKEUP.toLong()
+
+ @Test
+ fun dozeAmountOutputClampsTo1WhenDelayStarts() {
+ notificationWakeUpCoordinator.setWakingUp(true, requestDelayedAnimation = true)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
+
+ // verify further doze amount changes have no effect on output
+ setDozeAmount(0.5f)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
+ }
+
+ @Test
+ fun verifyDozeAmountOutputTracksDelay() {
+ dozeAmountOutputClampsTo1WhenDelayStarts()
+
+ // Animator waiting the delay amount should not yet affect the output
+ animatorTestRule.advanceTimeBy(delayedDozeDelay)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
+
+ // input doze amount change to 0 has no effect
+ setDozeAmount(0.0f)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
+
+ // Advancing the delay to 50% will cause the 50% output
+ animatorTestRule.advanceTimeBy(delayedDozeDuration / 2)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 0.5f, hideAmount = 0.5f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isFalse()
+
+ // Now advance delay to 100% completion; notifications become fully visible
+ animatorTestRule.advanceTimeBy(delayedDozeDuration / 2)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 0f, hideAmount = 0f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isFalse()
+
+ // Now advance delay to 200% completion -- should not invoke anything else
+ animatorTestRule.advanceTimeBy(delayedDozeDuration)
+ verify(stackScrollerController, never()).setDozeAmount(anyFloat())
+ verify(stackScrollerController, never()).setHideAmount(anyFloat(), anyFloat())
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isFalse()
+ }
+
+ @Test
+ fun verifyWakeUpListenerCallbacksWhenDozing() {
+ // prime internal state as dozing, then add the listener
+ setDozeAmount(1f)
+ notificationWakeUpCoordinator.addListener(wakeUpListener)
+
+ setDozeAmount(0.5f)
+ verify(wakeUpListener).onFullyHiddenChanged(eq(false))
+ verifyNoMoreInteractions(wakeUpListener)
+ clearInvocations(wakeUpListener)
+
+ setDozeAmount(0f)
+ verifyNoMoreInteractions(wakeUpListener)
+
+ setDozeAmount(0.5f)
+ verifyNoMoreInteractions(wakeUpListener)
+
+ setDozeAmount(1f)
+ verify(wakeUpListener).onFullyHiddenChanged(eq(true))
+ verifyNoMoreInteractions(wakeUpListener)
+ }
+
+ @Test
+ fun verifyWakeUpListenerCallbacksWhenDelayingAnimation() {
+ // prime internal state as dozing, then add the listener
+ setDozeAmount(1f)
+ notificationWakeUpCoordinator.addListener(wakeUpListener)
+
+ // setWakingUp() doesn't do anything yet
+ notificationWakeUpCoordinator.setWakingUp(true, requestDelayedAnimation = true)
+ verifyNoMoreInteractions(wakeUpListener)
+
+ // verify further doze amount changes have no effect
+ setDozeAmount(0.5f)
+ verifyNoMoreInteractions(wakeUpListener)
+
+ // advancing to just before the start time should not invoke the listener
+ animatorTestRule.advanceTimeBy(delayedDozeDelay - 1)
+ verifyNoMoreInteractions(wakeUpListener)
+
+ animatorTestRule.advanceTimeBy(1)
+ verify(wakeUpListener).onDelayedDozeAmountAnimationRunning(eq(true))
+ verifyNoMoreInteractions(wakeUpListener)
+ clearInvocations(wakeUpListener)
+
+ // input doze amount change to 0 has no effect
+ setDozeAmount(0.0f)
+ verifyNoMoreInteractions(wakeUpListener)
+
+ // Advancing the delay to 50% will cause notifications to no longer be fully hidden
+ animatorTestRule.advanceTimeBy(delayedDozeDuration / 2)
+ verify(wakeUpListener).onFullyHiddenChanged(eq(false))
+ verifyNoMoreInteractions(wakeUpListener)
+ clearInvocations(wakeUpListener)
+
+ // Now advance delay to 99.x% completion; notifications become fully visible
+ animatorTestRule.advanceTimeBy(delayedDozeDuration / 2 - 1)
+ verifyNoMoreInteractions(wakeUpListener)
+
+ // advance to 100%; animation no longer running
+ animatorTestRule.advanceTimeBy(1)
+ verify(wakeUpListener).onDelayedDozeAmountAnimationRunning(eq(false))
+ verifyNoMoreInteractions(wakeUpListener)
+ clearInvocations(wakeUpListener)
+
+ // Now advance delay to 200% completion -- should not invoke anything else
+ animatorTestRule.advanceTimeBy(delayedDozeDuration)
+ verifyNoMoreInteractions(wakeUpListener)
+ }
+
+ @Test
+ fun verifyDelayedDozeAmountCanBeOverridden() {
+ dozeAmountOutputClampsTo1WhenDelayStarts()
+
+ // input doze amount change to 0 has no effect
+ setDozeAmount(0.0f)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
+
+ // Advancing the delay to 50% will cause the 50% output
+ animatorTestRule.advanceTimeBy(delayedDozeDelay + delayedDozeDuration / 2)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 0.5f, hideAmount = 0.5f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isFalse()
+
+ // Enabling bypass and showing keyguard will override back to fully dozing/hidden
+ setBypassEnabled(true)
+ setStatusBarState(StatusBarState.KEYGUARD)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
+ }
+
+ @Test
+ fun verifyRemovingOverrideRestoresOtherwiseCalculatedDozeAmount() {
+ verifyDelayedDozeAmountCanBeOverridden()
+
+ // Disabling bypass will return back to the 50% value
+ setBypassEnabled(false)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 0.5f, hideAmount = 0.5f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isFalse()
+ }
+
private fun verifyStackScrollerDozeAndHideAmount(dozeAmount: Float, hideAmount: Float) {
// First verify that we did in-fact receive the correct values
- verify(stackScrollerController).setDozeAmount(dozeAmount)
- verify(stackScrollerController).setHideAmount(hideAmount, hideAmount)
+ verify(stackScrollerController).setDozeAmount(eased(dozeAmount))
+ verify(stackScrollerController).setHideAmount(hideAmount, eased(hideAmount))
// Now verify that there was just this ONE call to each of these methods
verify(stackScrollerController).setDozeAmount(anyFloat())
verify(stackScrollerController).setHideAmount(anyFloat(), anyFloat())
+ // clear for next check
+ clearInvocations(stackScrollerController)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
index 50b3fc7e1c42..d5c0c5564af6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
@@ -25,6 +25,8 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -48,6 +50,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
import com.android.systemui.statusbar.notification.collection.render.NodeController;
@@ -75,12 +78,15 @@ public class RankingCoordinatorTest extends SysuiTestCase {
@Mock private NodeController mAlertingHeaderController;
@Mock private NodeController mSilentNodeController;
@Mock private SectionHeaderController mSilentHeaderController;
+ @Mock private Pluggable.PluggableListener<NotifFilter> mInvalidationListener;
@Captor private ArgumentCaptor<NotifFilter> mNotifFilterCaptor;
+ @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
private NotificationEntry mEntry;
private NotifFilter mCapturedSuspendedFilter;
private NotifFilter mCapturedDozingFilter;
+ private StatusBarStateController.StateListener mStatusBarStateCallback;
private RankingCoordinator mRankingCoordinator;
private NotifSectioner mAlertingSectioner;
@@ -106,6 +112,10 @@ public class RankingCoordinatorTest extends SysuiTestCase {
verify(mNotifPipeline, times(2)).addPreGroupFilter(mNotifFilterCaptor.capture());
mCapturedSuspendedFilter = mNotifFilterCaptor.getAllValues().get(0);
mCapturedDozingFilter = mNotifFilterCaptor.getAllValues().get(1);
+ mCapturedDozingFilter.setInvalidationListener(mInvalidationListener);
+
+ verify(mStatusBarStateController, times(1)).addCallback(mStateListenerCaptor.capture());
+ mStatusBarStateCallback = mStateListenerCaptor.getAllValues().get(0);
mAlertingSectioner = mRankingCoordinator.getAlertingSectioner();
mSilentSectioner = mRankingCoordinator.getSilentSectioner();
@@ -170,6 +180,13 @@ public class RankingCoordinatorTest extends SysuiTestCase {
// THEN don't filter out the notification
assertFalse(mCapturedDozingFilter.shouldFilterOut(mEntry, 0));
+
+ // WHEN it's not dozing and doze amount is 1
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarStateController.getDozeAmount()).thenReturn(1f);
+
+ // THEN filter out the notification
+ assertTrue(mCapturedDozingFilter.shouldFilterOut(mEntry, 0));
}
@Test
@@ -267,6 +284,27 @@ public class RankingCoordinatorTest extends SysuiTestCase {
verify(mSilentHeaderController, times(2)).setClearSectionEnabled(eq(false));
}
+ @Test
+ public void statusBarStateCallbackTest() {
+ mStatusBarStateCallback.onDozeAmountChanged(1f, 1f);
+ verify(mInvalidationListener, times(1))
+ .onPluggableInvalidated(mCapturedDozingFilter, "dozeAmount changed to one");
+ reset(mInvalidationListener);
+
+ mStatusBarStateCallback.onDozeAmountChanged(1f, 1f);
+ verify(mInvalidationListener, never()).onPluggableInvalidated(any(), any());
+ reset(mInvalidationListener);
+
+ mStatusBarStateCallback.onDozeAmountChanged(0.6f, 0.6f);
+ verify(mInvalidationListener, times(1))
+ .onPluggableInvalidated(mCapturedDozingFilter, "dozeAmount changed to not one");
+ reset(mInvalidationListener);
+
+ mStatusBarStateCallback.onDozeAmountChanged(0f, 0f);
+ verify(mInvalidationListener, never()).onPluggableInvalidated(any(), any());
+ reset(mInvalidationListener);
+ }
+
private void assertInSection(NotificationEntry entry, NotifSectioner section) {
for (NotifSectioner current: mSections) {
if (current == section) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 31a1e4f9a4d9..775d2673ab18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -57,6 +57,8 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
+import dagger.Lazy;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -66,8 +68,6 @@ import org.mockito.stubbing.Answer;
import java.util.Optional;
-import dagger.Lazy;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
@@ -153,7 +153,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
// Trying to open it does nothing.
mSbcqCallbacks.animateExpandNotificationsPanel();
- verify(mNotificationPanelViewController, never()).expandShadeToNotifications();
+ verify(mNotificationPanelViewController, never()).expandToNotifications();
mSbcqCallbacks.animateExpandSettingsPanel(null);
verify(mNotificationPanelViewController, never()).expand(anyBoolean());
}
@@ -171,9 +171,9 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
// Can now be opened.
mSbcqCallbacks.animateExpandNotificationsPanel();
- verify(mNotificationPanelViewController).expandShadeToNotifications();
+ verify(mNotificationPanelViewController).expandToNotifications();
mSbcqCallbacks.animateExpandSettingsPanel(null);
- verify(mNotificationPanelViewController).expandWithQs();
+ verify(mNotificationPanelViewController).expandToQs();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 7db219719bf0..32f0adfa1954 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -315,6 +315,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private ViewRootImpl mViewRootImpl;
@Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
@Mock private UserTracker mUserTracker;
+ @Mock private FingerprintManager mFingerprintManager;
@Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
@Mock IPowerManager mPowerManagerService;
@@ -532,7 +533,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mCameraLauncherLazy,
() -> mLightRevealScrimViewModel,
mAlternateBouncerInteractor,
- mUserTracker
+ mUserTracker,
+ () -> mFingerprintManager
) {
@Override
protected ViewRootImpl getViewRootImpl() {
@@ -855,7 +857,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
mOnBackInvokedCallback.capture());
- when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true);
+ when(mNotificationPanelViewController.canBeCollapsed()).thenReturn(true);
mOnBackInvokedCallback.getValue().onBackInvoked();
verify(mShadeController).animateCollapseShade();
}
@@ -875,7 +877,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
OnBackAnimationCallback onBackAnimationCallback =
(OnBackAnimationCallback) (mOnBackInvokedCallback.getValue());
- when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true);
+ when(mNotificationPanelViewController.canBeCollapsed()).thenReturn(true);
BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 100.0f, 1.0f, BackEvent.EDGE_LEFT);
onBackAnimationCallback.onBackProgressed(fakeSwipeInFromLeftEdge);
@@ -897,7 +899,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
OnBackAnimationCallback onBackAnimationCallback =
(OnBackAnimationCallback) (mOnBackInvokedCallback.getValue());
- when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true);
+ when(mNotificationPanelViewController.canBeCollapsed()).thenReturn(true);
BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 10.0f, 0.0f, BackEvent.EDGE_LEFT);
onBackAnimationCallback.onBackProgressed(fakeSwipeInFromLeftEdge);
@@ -1233,7 +1235,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
setFoldedStates(FOLD_STATE_FOLDED);
setGoToSleepStates(FOLD_STATE_FOLDED);
mCentralSurfaces.setBarStateForTest(SHADE);
- when(mNotificationPanelViewController.isShadeFullyOpen()).thenReturn(true);
+ when(mNotificationPanelViewController.isShadeFullyExpanded()).thenReturn(true);
setDeviceState(FOLD_STATE_UNFOLDED);
@@ -1245,7 +1247,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
setFoldedStates(FOLD_STATE_FOLDED);
setGoToSleepStates(FOLD_STATE_FOLDED);
mCentralSurfaces.setBarStateForTest(KEYGUARD);
- when(mNotificationPanelViewController.isShadeFullyOpen()).thenReturn(true);
+ when(mNotificationPanelViewController.isShadeFullyExpanded()).thenReturn(true);
setDeviceState(FOLD_STATE_UNFOLDED);
@@ -1258,7 +1260,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
setFoldedStates(FOLD_STATE_FOLDED);
setGoToSleepStates(FOLD_STATE_FOLDED);
mCentralSurfaces.setBarStateForTest(SHADE);
- when(mNotificationPanelViewController.isShadeFullyOpen()).thenReturn(false);
+ when(mNotificationPanelViewController.isShadeFullyExpanded()).thenReturn(false);
setDeviceState(FOLD_STATE_UNFOLDED);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index e5e5d94ab595..3372dc3e7959 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -39,6 +39,7 @@ import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeHeadsUpTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.HeadsUpStatusBarView;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
@@ -66,6 +67,7 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
mock(NotificationStackScrollLayoutController.class);
private final NotificationPanelViewController mPanelView =
mock(NotificationPanelViewController.class);
+ private final ShadeHeadsUpTracker mShadeHeadsUpTracker = mock(ShadeHeadsUpTracker.class);
private final DarkIconDispatcher mDarkIconDispatcher = mock(DarkIconDispatcher.class);
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private NotificationTestHelper mTestHelper;
@@ -102,6 +104,7 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
mCommandQueue = mock(CommandQueue.class);
mNotificationRoundnessManager = mock(NotificationRoundnessManager.class);
mFeatureFlag = mock(FeatureFlags.class);
+ when(mPanelView.getShadeHeadsUpTracker()).thenReturn(mShadeHeadsUpTracker);
when(mFeatureFlag.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES)).thenReturn(true);
mHeadsUpAppearanceController = new HeadsUpAppearanceController(
mock(NotificationIconAreaController.class),
@@ -212,15 +215,15 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
public void testDestroy() {
reset(mHeadsUpManager);
reset(mDarkIconDispatcher);
- reset(mPanelView);
+ reset(mShadeHeadsUpTracker);
reset(mStackScrollerController);
mHeadsUpAppearanceController.onViewDetached();
verify(mHeadsUpManager).removeListener(any());
verify(mDarkIconDispatcher).removeDarkReceiver((DarkIconDispatcher.DarkReceiver) any());
- verify(mPanelView).removeTrackingHeadsUpListener(any());
- verify(mPanelView).setHeadsUpAppearanceController(isNull());
+ verify(mShadeHeadsUpTracker).removeTrackingHeadsUpListener(any());
+ verify(mShadeHeadsUpTracker).setHeadsUpAppearanceController(isNull());
verify(mStackScrollerController).removeOnExpandedHeightChangedListener(any());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 5bb25f5425a7..e83e50d65ae9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -45,6 +45,7 @@ import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeNotificationPresenter;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -110,9 +111,12 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase {
mock(NotificationStackScrollLayout.class));
when(notificationShadeWindowView.getResources()).thenReturn(mContext.getResources());
+ NotificationPanelViewController npvc = mock(NotificationPanelViewController.class);
+ when(npvc.getShadeNotificationPresenter())
+ .thenReturn(mock(ShadeNotificationPresenter.class));
mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(
mContext,
- mock(NotificationPanelViewController.class),
+ npvc,
mock(QuickSettingsController.class),
mock(HeadsUpManagerPhone.class),
notificationShadeWindowView,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index 0b2028532307..1fdcf7f27dbf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -34,6 +34,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.validMobileEvent
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeSubscriptionManagerProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
@@ -89,6 +90,7 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
private val fakeNetworkEventsFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
private val mobileMappings = FakeMobileMappingsProxy()
+ private val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
private val scope = CoroutineScope(IMMEDIATE)
@@ -117,6 +119,7 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
MobileConnectionsRepositoryImpl(
connectivityRepository,
subscriptionManager,
+ subscriptionManagerProxy,
telephonyManager,
logger,
summaryLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index d65277f37ec4..ddff17aef2de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -47,6 +47,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierCon
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeSubscriptionManagerProxy
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
@@ -98,6 +99,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
@Mock private lateinit var logBufferFactory: TableLogBufferFactory
private val mobileMappings = FakeMobileMappingsProxy()
+ private val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
private val scope = CoroutineScope(IMMEDIATE)
@@ -179,6 +181,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
MobileConnectionsRepositoryImpl(
connectivityRepository,
subscriptionManager,
+ subscriptionManagerProxy,
telephonyManager,
logger,
summaryLogger,
@@ -662,6 +665,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
var latest: Int? = null
val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
+ assertThat(latest).isEqualTo(INVALID_SUBSCRIPTION_ID)
+
fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
receiver.onReceive(
context,
@@ -686,6 +691,42 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
}
@Test
+ fun defaultDataSubId_fetchesInitialValueOnStart() =
+ runBlocking(IMMEDIATE) {
+ subscriptionManagerProxy.defaultDataSubId = 2
+ var latest: Int? = null
+ val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun defaultDataSubId_fetchesCurrentOnRestart() =
+ runBlocking(IMMEDIATE) {
+ subscriptionManagerProxy.defaultDataSubId = 2
+ var latest: Int? = null
+ var job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(2)
+
+ job.cancel()
+
+ // Collectors go away but come back later
+
+ latest = null
+
+ subscriptionManagerProxy.defaultDataSubId = 1
+
+ job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(1)
+
+ job.cancel()
+ }
+
+ @Test
fun mobileIsDefault_startsAsFalse() {
assertThat(underTest.mobileIsDefault.value).isFalse()
}
@@ -902,6 +943,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
MobileConnectionsRepositoryImpl(
connectivityRepository,
subscriptionManager,
+ subscriptionManagerProxy,
telephonyManager,
logger,
summaryLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt
new file mode 100644
index 000000000000..3dc7de688446
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.util
+
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+
+/** Fake of [SubscriptionManagerProxy] for easy testing */
+class FakeSubscriptionManagerProxy(
+ /** Set the default data subId to be returned in [getDefaultDataSubscriptionId] */
+ var defaultDataSubId: Int = INVALID_SUBSCRIPTION_ID
+) : SubscriptionManagerProxy {
+ override fun getDefaultDataSubscriptionId(): Int = defaultDataSubId
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 391c8ca4d286..01e94baab7c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -62,7 +62,7 @@ import android.window.OnBackInvokedDispatcher;
import android.window.WindowOnBackInvokedDispatcher;
import androidx.annotation.NonNull;
-import androidx.core.animation.AnimatorTestRule;
+import androidx.core.animation.AnimatorTestRule2;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
@@ -110,7 +110,7 @@ public class RemoteInputViewTest extends SysuiTestCase {
private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
@ClassRule
- public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+ public static AnimatorTestRule2 mAnimatorTestRule = new AnimatorTestRule2();
@Before
public void setUp() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java
index 667099718788..eb932d29bf5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java
@@ -110,13 +110,14 @@ public class TouchInsetManagerTest extends SysuiTestCase {
clearInvocations(mAttachedSurfaceControl);
when(view.isAttachedToWindow()).thenReturn(false);
+ when(view.getRootSurfaceControl()).thenReturn(null);
// Trigger detachment and verify touchable region is set.
listener.getValue().onViewDetachedFromWindow(view);
mFakeExecutor.runAllReady();
- verify(mAttachedSurfaceControl).setTouchableRegion(any());
+ verify(mAttachedSurfaceControl).setTouchableRegion(eq(null));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
index a87e61aae207..dfbd61bd057b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
@@ -32,6 +32,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerReposito
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.shade.ShadeFoldAnimator
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -50,6 +51,8 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
@@ -79,6 +82,8 @@ class FoldAodAnimationControllerTest : SysuiTestCase() {
@Mock private lateinit var commandQueue: CommandQueue
+ @Mock lateinit var shadeFoldAnimator: ShadeFoldAnimator
+
@Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
private lateinit var deviceStates: FoldableDeviceStates
@@ -95,17 +100,17 @@ class FoldAodAnimationControllerTest : SysuiTestCase() {
deviceStates = FoldableTestUtils.findDeviceStates(context)
// TODO(b/254878364): remove this call to NPVC.getView()
- whenever(notificationPanelViewController.view).thenReturn(viewGroup)
+ whenever(notificationPanelViewController.shadeFoldAnimator).thenReturn(shadeFoldAnimator)
+ whenever(shadeFoldAnimator.view).thenReturn(viewGroup)
whenever(viewGroup.viewTreeObserver).thenReturn(viewTreeObserver)
whenever(wakefulnessLifecycle.lastSleepReason)
.thenReturn(PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD)
whenever(centralSurfaces.notificationPanelViewController)
.thenReturn(notificationPanelViewController)
- whenever(notificationPanelViewController.startFoldToAodAnimation(any(), any(), any()))
- .then {
- val onActionStarted = it.arguments[0] as Runnable
- onActionStarted.run()
- }
+ whenever(shadeFoldAnimator.startFoldToAodAnimation(any(), any(), any())).then {
+ val onActionStarted = it.arguments[0] as Runnable
+ onActionStarted.run()
+ }
keyguardRepository = FakeKeyguardRepository()
val featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
@@ -174,6 +179,28 @@ class FoldAodAnimationControllerTest : SysuiTestCase() {
}
@Test
+ fun onFolded_onScreenTurningOnInvokedTwice_doesNotLogLatency() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.listenForDozing(this)
+ keyguardRepository.setDozing(true)
+ setAodEnabled(enabled = true)
+
+ yield()
+
+ fold()
+ simulateScreenTurningOn()
+ reset(latencyTracker)
+
+ // This can happen > 1 time if the prox sensor is covered
+ simulateScreenTurningOn()
+
+ verify(latencyTracker, never()).onActionStart(any())
+ verify(latencyTracker, never()).onActionEnd(any())
+
+ job.cancel()
+ }
+
+ @Test
fun onFolded_animationCancelled_doesNotLogLatency() =
runBlocking(IMMEDIATE) {
val job = underTest.listenForDozing(this)
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 4af6b89eb939..46fc7628aae4 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1176,7 +1176,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// structure is taken. This causes only one fill request per burst of focus changes.
cancelCurrentRequestLocked();
- if (mClassificationState.mHintsToAutofillIdMap == null) {
+ if (mService.getMaster().isPccClassificationEnabled()
+ && mClassificationState.mHintsToAutofillIdMap == null) {
if (sVerbose) {
Slog.v(TAG, "triggering field classification");
}
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 b338d89a0169..1363ef31c68d 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -1046,18 +1046,30 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
*/
void showToastWhereUidIsRunning(int uid, String text, @Toast.Duration int duration,
Looper looper) {
+ ArrayList<Integer> displayIdsForUid = getDisplayIdsWhereUidIsRunning(uid);
+ if (displayIdsForUid.isEmpty()) {
+ return;
+ }
+ DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ for (int i = 0; i < displayIdsForUid.size(); i++) {
+ Display display = displayManager.getDisplay(displayIdsForUid.get(i));
+ if (display != null && display.isValid()) {
+ Toast.makeText(mContext.createDisplayContext(display), looper, text,
+ duration).show();
+ }
+ }
+ }
+
+ private ArrayList<Integer> getDisplayIdsWhereUidIsRunning(int uid) {
+ ArrayList<Integer> displayIdsForUid = new ArrayList<>();
synchronized (mVirtualDeviceLock) {
- DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
for (int i = 0; i < mVirtualDisplays.size(); i++) {
if (mVirtualDisplays.valueAt(i).getWindowPolicyController().containsUid(uid)) {
- Display display = displayManager.getDisplay(mVirtualDisplays.keyAt(i));
- if (display != null && display.isValid()) {
- Toast.makeText(mContext.createDisplayContext(display), looper, text,
- duration).show();
- }
+ displayIdsForUid.add(mVirtualDisplays.keyAt(i));
}
}
}
+ return displayIdsForUid;
}
boolean isDisplayOwnedByVirtualDevice(int displayId) {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index e0d1c1e70b34..73dbb86ae7cc 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -73,6 +73,9 @@ import android.content.pm.ProviderInfo;
import android.content.pm.UserInfo;
import android.content.res.ObbInfo;
import android.database.ContentObserver;
+import android.media.MediaCodecList;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.Binder;
@@ -954,10 +957,27 @@ class StorageManagerService extends IStorageManager.Stub
}
}
+ private boolean isHevcDecoderSupported() {
+ MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+ MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
+ for (MediaCodecInfo codecInfo : codecInfos) {
+ if (codecInfo.isEncoder()) {
+ continue;
+ }
+ String[] supportedTypes = codecInfo.getSupportedTypes();
+ for (String type : supportedTypes) {
+ if (type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private void configureTranscoding() {
// See MediaProvider TranscodeHelper#getBooleanProperty for more information
boolean transcodeEnabled = false;
- boolean defaultValue = true;
+ boolean defaultValue = isHevcDecoderSupported() ? true : false;
if (SystemProperties.getBoolean("persist.sys.fuse.transcode_user_control", false)) {
transcodeEnabled = SystemProperties.getBoolean("persist.sys.fuse.transcode_enabled",
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 78d4708e70a2..e8c85ce68f22 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -1074,9 +1074,10 @@ public class VcnManagementService extends IVcnManagementService.Stub {
subGrp, mLastSnapshot, mConfigs.get(subGrp));
for (int restrictedTransport : restrictedTransports) {
if (ncCopy.hasTransport(restrictedTransport)) {
- if (restrictedTransport == TRANSPORT_CELLULAR) {
- // Only make a cell network as restricted when the VCN is in
- // active mode.
+ if (restrictedTransport == TRANSPORT_CELLULAR
+ || restrictedTransport == TRANSPORT_TEST) {
+ // For cell or test network, only mark it as restricted when
+ // the VCN is in active mode.
isRestricted |= (vcn.getStatus() == VCN_STATUS_CODE_ACTIVE);
} else {
isRestricted = true;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8d8ed196be18..a3e582046b03 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -6984,8 +6984,9 @@ public class ActivityManagerService extends IActivityManager.Stub
mActivityTaskManager.onScreenAwakeChanged(isAwake);
mOomAdjProfiler.onWakefulnessChanged(wakefulness);
mOomAdjuster.onWakefulnessChanged(wakefulness);
+
+ updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
}
- updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
}
}
@@ -8645,13 +8646,16 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ boolean recoverable = eventType.equals("native_recoverable_crash");
+
EventLogTags.writeAmCrash(Binder.getCallingPid(),
UserHandle.getUserId(Binder.getCallingUid()), processName,
r == null ? -1 : r.info.flags,
crashInfo.exceptionClassName,
crashInfo.exceptionMessage,
crashInfo.throwFileName,
- crashInfo.throwLineNumber);
+ crashInfo.throwLineNumber,
+ recoverable ? 1 : 0);
int processClassEnum = processName.equals("system_server") ? ServerProtoEnums.SYSTEM_SERVER
: (r != null) ? r.getProcessClassEnum()
@@ -8719,7 +8723,13 @@ public class ActivityManagerService extends IActivityManager.Stub
eventType, r, processName, null, null, null, null, null, null, crashInfo,
new Float(loadingProgress), incrementalMetrics, null);
- mAppErrors.crashApplication(r, crashInfo);
+ // For GWP-ASan recoverable crashes, don't make the app crash (the whole point of
+ // 'recoverable' is that the app doesn't crash). Normally, for nonrecoreable native crashes,
+ // debuggerd will terminate the process, but there's a backup where ActivityManager will
+ // also kill it. Avoid that.
+ if (!recoverable) {
+ mAppErrors.crashApplication(r, crashInfo);
+ }
}
public void handleApplicationStrictModeViolation(
@@ -18780,7 +18790,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final IApplicationThread thread = app.getOnewayThread();
if (thread != null) {
mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app,
- OomAdjuster.OOM_ADJ_REASON_NONE);
+ CachedAppOptimizer.UNFREEZE_REASON_PING);
pingCount.incrementAndGet();
try {
thread.schedulePing(pongCallback);
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index dd6598f8e521..ccd739006713 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -45,7 +45,6 @@ import static com.android.server.am.ActivityManagerService.appendMemInfo;
import static com.android.server.am.ActivityManagerService.getKsmInfo;
import static com.android.server.am.ActivityManagerService.stringifyKBSize;
import static com.android.server.am.LowMemDetector.ADJ_MEM_FACTOR_NOTHING;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_NONE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
import static com.android.server.wm.ActivityTaskManagerService.DUMP_ACTIVITIES_CMD;
@@ -1119,7 +1118,7 @@ public class AppProfiler {
Slog.v(TAG_OOM_ADJ, msg + app.processName + " to " + level);
}
mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app,
- OOM_ADJ_REASON_NONE);
+ CachedAppOptimizer.UNFREEZE_REASON_TRIM_MEMORY);
thread.scheduleTrimMemory(level);
} catch (RemoteException e) {
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index fcddff0e5ebc..4b8dc99c67d9 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -37,7 +37,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_L
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER;
import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
import android.annotation.NonNull;
@@ -837,7 +836,7 @@ public class BroadcastQueueImpl extends BroadcastQueue {
}
} else if (filter.receiverList.app != null) {
mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(filter.receiverList.app,
- OOM_ADJ_REASON_START_RECEIVER);
+ CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER);
}
try {
@@ -1131,7 +1130,8 @@ public class BroadcastQueueImpl extends BroadcastQueue {
if (sendResult) {
if (r.callerApp != null) {
mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
- r.callerApp, OOM_ADJ_REASON_FINISH_RECEIVER);
+ r.callerApp,
+ CachedAppOptimizer.UNFREEZE_REASON_FINISH_RECEIVER);
}
try {
if (DEBUG_BROADCAST) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 93173abeb106..5010ec03285f 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -37,7 +37,6 @@ import static com.android.server.am.BroadcastRecord.getReceiverPackageName;
import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
import static com.android.server.am.BroadcastRecord.getReceiverUid;
import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER;
import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
import android.annotation.NonNull;
@@ -929,7 +928,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
final IApplicationThread thread = (app != null) ? app.getOnewayThread() : null;
if (thread != null) {
mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
- app, OOM_ADJ_REASON_FINISH_RECEIVER);
+ app, CachedAppOptimizer.UNFREEZE_REASON_FINISH_RECEIVER);
if (r.shareIdentity && app.uid != r.callingUid) {
mService.mPackageManagerInt.grantImplicitAccess(r.userId, r.intent,
UserHandle.getAppId(app.uid), r.callingUid, true);
@@ -1515,7 +1514,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(queue.app,
- OOM_ADJ_REASON_START_RECEIVER);
+ CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER);
if (queue.runningOomAdjusted) {
queue.app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index b293bcf0e17d..6c9f602bbc94 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -23,6 +23,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FREEZER;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ApplicationExitInfo;
@@ -54,6 +55,8 @@ import com.android.server.ServiceThread;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumMap;
@@ -94,6 +97,70 @@ public final class CachedAppOptimizer {
@VisibleForTesting static final String KEY_FREEZER_EXEMPT_INST_PKG =
"freeze_exempt_inst_pkg";
+
+ static final int UNFREEZE_REASON_NONE =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_NONE;
+ static final int UNFREEZE_REASON_ACTIVITY =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_ACTIVITY;
+ static final int UNFREEZE_REASON_FINISH_RECEIVER =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FINISH_RECEIVER;
+ static final int UNFREEZE_REASON_START_RECEIVER =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_START_RECEIVER;
+ static final int UNFREEZE_REASON_BIND_SERVICE =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BIND_SERVICE;
+ static final int UNFREEZE_REASON_UNBIND_SERVICE =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_UNBIND_SERVICE;
+ static final int UNFREEZE_REASON_START_SERVICE =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_START_SERVICE;
+ static final int UNFREEZE_REASON_GET_PROVIDER =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_GET_PROVIDER;
+ static final int UNFREEZE_REASON_REMOVE_PROVIDER =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_REMOVE_PROVIDER;
+ static final int UNFREEZE_REASON_UI_VISIBILITY =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_UI_VISIBILITY;
+ static final int UNFREEZE_REASON_ALLOWLIST =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_ALLOWLIST;
+ static final int UNFREEZE_REASON_PROCESS_BEGIN =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_PROCESS_BEGIN;
+ static final int UNFREEZE_REASON_PROCESS_END =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_PROCESS_END;
+ static final int UNFREEZE_REASON_TRIM_MEMORY =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_TRIM_MEMORY;
+ static final int UNFREEZE_REASON_PING =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_PING;
+ static final int UNFREEZE_REASON_FILE_LOCKS =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FILE_LOCKS;
+ static final int UNFREEZE_REASON_FILE_LOCK_CHECK_FAILURE =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FILE_LOCK_CHECK_FAILURE;
+ static final int UNFREEZE_REASON_BINDER_TXNS =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BINDER_TXNS;
+ static final int UNFREEZE_REASON_FEATURE_FLAGS =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FEATURE_FLAGS;
+
+ @IntDef(prefix = {"UNFREEZE_REASON_"}, value = {
+ UNFREEZE_REASON_NONE,
+ UNFREEZE_REASON_ACTIVITY,
+ UNFREEZE_REASON_FINISH_RECEIVER,
+ UNFREEZE_REASON_START_RECEIVER,
+ UNFREEZE_REASON_BIND_SERVICE,
+ UNFREEZE_REASON_UNBIND_SERVICE,
+ UNFREEZE_REASON_START_SERVICE,
+ UNFREEZE_REASON_GET_PROVIDER,
+ UNFREEZE_REASON_REMOVE_PROVIDER,
+ UNFREEZE_REASON_UI_VISIBILITY,
+ UNFREEZE_REASON_ALLOWLIST,
+ UNFREEZE_REASON_PROCESS_BEGIN,
+ UNFREEZE_REASON_PROCESS_END,
+ UNFREEZE_REASON_TRIM_MEMORY,
+ UNFREEZE_REASON_PING,
+ UNFREEZE_REASON_FILE_LOCKS,
+ UNFREEZE_REASON_FILE_LOCK_CHECK_FAILURE,
+ UNFREEZE_REASON_BINDER_TXNS,
+ UNFREEZE_REASON_FEATURE_FLAGS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UnfreezeReason {}
+
// RSS Indices
private static final int RSS_TOTAL_INDEX = 0;
private static final int RSS_FILE_INDEX = 1;
@@ -888,7 +955,7 @@ public final class CachedAppOptimizer {
}
if (!enable && opt.isFrozen()) {
- unfreezeAppLSP(process, OomAdjuster.OOM_ADJ_REASON_NONE);
+ unfreezeAppLSP(process, UNFREEZE_REASON_FEATURE_FLAGS);
// Set freezerOverride *after* calling unfreezeAppLSP (it resets the flag)
opt.setFreezerOverride(true);
@@ -1194,7 +1261,7 @@ public final class CachedAppOptimizer {
// This will ensure app will be out of the freezer for at least mFreezerDebounceTimeout.
@GuardedBy("mAm")
- void unfreezeTemporarily(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
+ void unfreezeTemporarily(ProcessRecord app, @UnfreezeReason int reason) {
if (mUseFreezer) {
synchronized (mProcLock) {
if (app.mOptRecord.isFrozen() || app.mOptRecord.isPendingFreeze()) {
@@ -1224,7 +1291,7 @@ public final class CachedAppOptimizer {
}
@GuardedBy({"mAm", "mProcLock", "mFreezerLock"})
- void unfreezeAppInternalLSP(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
+ void unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason) {
final int pid = app.getPid();
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
if (opt.isPendingFreeze()) {
@@ -1318,7 +1385,7 @@ public final class CachedAppOptimizer {
}
@GuardedBy({"mAm", "mProcLock"})
- void unfreezeAppLSP(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
+ void unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason) {
synchronized (mFreezerLock) {
unfreezeAppInternalLSP(app, reason);
}
@@ -1950,7 +2017,7 @@ public final class CachedAppOptimizer {
+ name + "(" + pid + "): " + e);
synchronized (mAm) {
synchronized (mProcLock) {
- unfreezeAppLSP(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
+ unfreezeAppLSP(proc, UNFREEZE_REASON_FILE_LOCK_CHECK_FAILURE);
}
}
}
@@ -1975,10 +2042,11 @@ public final class CachedAppOptimizer {
}
@GuardedBy({"mAm", "mProcLock"})
- private void rescheduleFreeze(final ProcessRecord proc, final String reason) {
+ private void rescheduleFreeze(final ProcessRecord proc, final String reason,
+ @UnfreezeReason int reasonCode) {
Slog.d(TAG_AM, "Reschedule freeze for process " + proc.getPid()
+ " " + proc.processName + " (" + reason + ")");
- unfreezeAppLSP(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
+ unfreezeAppLSP(proc, reasonCode);
freezeAppAsyncLSP(proc);
}
@@ -2024,7 +2092,7 @@ public final class CachedAppOptimizer {
// transactions that might be pending.
try {
if (freezeBinder(pid, true, FREEZE_BINDER_TIMEOUT_MS) != 0) {
- rescheduleFreeze(proc, "outstanding txns");
+ rescheduleFreeze(proc, "outstanding txns", UNFREEZE_REASON_BINDER_TXNS);
return;
}
} catch (RuntimeException e) {
@@ -2076,7 +2144,8 @@ public final class CachedAppOptimizer {
pid,
name,
unfrozenDuration,
- FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__NONE);
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__NONE,
+ UNFREEZE_REASON_NONE);
}
try {
@@ -2085,7 +2154,7 @@ public final class CachedAppOptimizer {
if ((freezeInfo & TXNS_PENDING_WHILE_FROZEN) != 0) {
synchronized (mProcLock) {
- rescheduleFreeze(proc, "new pending txns");
+ rescheduleFreeze(proc, "new pending txns", UNFREEZE_REASON_BINDER_TXNS);
}
return;
}
@@ -2102,7 +2171,7 @@ public final class CachedAppOptimizer {
}
private void reportUnfreeze(int pid, int frozenDuration, String processName,
- @OomAdjuster.OomAdjReason int reason) {
+ @UnfreezeReason int reason) {
EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName);
@@ -2114,38 +2183,8 @@ public final class CachedAppOptimizer {
pid,
processName,
frozenDuration,
- getUnfreezeReasonCode(reason));
- }
- }
-
- private int getUnfreezeReasonCode(@OomAdjuster.OomAdjReason int oomAdjReason) {
- switch (oomAdjReason) {
- case OomAdjuster.OOM_ADJ_REASON_ACTIVITY:
- return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__ACTIVITY;
- case OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER:
- return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__FINISH_RECEIVER;
- case OomAdjuster.OOM_ADJ_REASON_START_RECEIVER:
- return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__START_RECEIVER;
- case OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE:
- return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__BIND_SERVICE;
- case OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE:
- return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__UNBIND_SERVICE;
- case OomAdjuster.OOM_ADJ_REASON_START_SERVICE:
- return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__START_SERVICE;
- case OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER:
- return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__GET_PROVIDER;
- case OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER:
- return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__REMOVE_PROVIDER;
- case OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY:
- return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__UI_VISIBILITY;
- case OomAdjuster.OOM_ADJ_REASON_ALLOWLIST:
- return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__ALLOWLIST;
- case OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN:
- return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__PROCESS_BEGIN;
- case OomAdjuster.OOM_ADJ_REASON_PROCESS_END:
- return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__PROCESS_END;
- default:
- return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__NONE;
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__NONE, // deprecated
+ reason);
}
}
@@ -2171,7 +2210,7 @@ public final class CachedAppOptimizer {
Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks "
+ pr.processName + " (" + blocked + ")");
// Found at least one blocked non-cached process
- unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ unfreezeAppLSP(app, UNFREEZE_REASON_FILE_LOCKS);
break;
}
}
@@ -2207,4 +2246,35 @@ public final class CachedAppOptimizer {
mPidCompacting = -1;
}
}
+
+ static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjuster.OomAdjReason int oomAdjReason) {
+ switch (oomAdjReason) {
+ case OomAdjuster.OOM_ADJ_REASON_ACTIVITY:
+ return UNFREEZE_REASON_ACTIVITY;
+ case OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER:
+ return UNFREEZE_REASON_FINISH_RECEIVER;
+ case OomAdjuster.OOM_ADJ_REASON_START_RECEIVER:
+ return UNFREEZE_REASON_START_RECEIVER;
+ case OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE:
+ return UNFREEZE_REASON_BIND_SERVICE;
+ case OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE:
+ return UNFREEZE_REASON_UNBIND_SERVICE;
+ case OomAdjuster.OOM_ADJ_REASON_START_SERVICE:
+ return UNFREEZE_REASON_START_SERVICE;
+ case OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER:
+ return UNFREEZE_REASON_GET_PROVIDER;
+ case OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER:
+ return UNFREEZE_REASON_REMOVE_PROVIDER;
+ case OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY:
+ return UNFREEZE_REASON_UI_VISIBILITY;
+ case OomAdjuster.OOM_ADJ_REASON_ALLOWLIST:
+ return UNFREEZE_REASON_ALLOWLIST;
+ case OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN:
+ return UNFREEZE_REASON_PROCESS_BEGIN;
+ case OomAdjuster.OOM_ADJ_REASON_PROCESS_END:
+ return UNFREEZE_REASON_PROCESS_END;
+ default:
+ return UNFREEZE_REASON_NONE;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 50841ae4488c..81b242155bac 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -53,7 +53,7 @@ option java_package com.android.server.am
30037 am_process_start_timeout (User|1|5),(PID|1|5),(UID|1|5),(Process Name|3)
# Unhandled exception
-30039 am_crash (User|1|5),(PID|1|5),(Process Name|3),(Flags|1|5),(Exception|3),(Message|3),(File|3),(Line|1|5)
+30039 am_crash (User|1|5),(PID|1|5),(Process Name|3),(Flags|1|5),(Exception|3),(Message|3),(File|3),(Line|1|5),(Recoverable|1|5)
# Log.wtf() called
30040 am_wtf (User|1|5),(PID|1|5),(Process Name|3),(Flags|1|5),(Tag|3),(Message|3)
diff --git a/services/core/java/com/android/server/am/NativeCrashListener.java b/services/core/java/com/android/server/am/NativeCrashListener.java
index 94eb07611037..cd119e7e3fbc 100644
--- a/services/core/java/com/android/server/am/NativeCrashListener.java
+++ b/services/core/java/com/android/server/am/NativeCrashListener.java
@@ -64,12 +64,15 @@ final class NativeCrashListener extends Thread {
class NativeCrashReporter extends Thread {
ProcessRecord mApp;
int mSignal;
+ boolean mGwpAsanRecoverableCrash;
String mCrashReport;
- NativeCrashReporter(ProcessRecord app, int signal, String report) {
+ NativeCrashReporter(ProcessRecord app, int signal, boolean gwpAsanRecoverableCrash,
+ String report) {
super("NativeCrashReport");
mApp = app;
mSignal = signal;
+ mGwpAsanRecoverableCrash = gwpAsanRecoverableCrash;
mCrashReport = report;
}
@@ -85,7 +88,9 @@ final class NativeCrashListener extends Thread {
ci.stackTrace = mCrashReport;
if (DEBUG) Slog.v(TAG, "Calling handleApplicationCrash()");
- mAm.handleApplicationCrashInner("native_crash", mApp, mApp.processName, ci);
+ mAm.handleApplicationCrashInner(
+ mGwpAsanRecoverableCrash ? "native_recoverable_crash" : "native_crash",
+ mApp, mApp.processName, ci);
if (DEBUG) Slog.v(TAG, "<-- handleApplicationCrash() returned");
} catch (Exception e) {
Slog.e(TAG, "Unable to report native crash", e);
@@ -207,9 +212,14 @@ final class NativeCrashListener extends Thread {
// permits crash_dump to connect to it. This allows us to trust the
// received values.
- // first, the pid and signal number
- int headerBytes = readExactly(fd, buf, 0, 8);
- if (headerBytes != 8) {
+ // Activity Manager protocol:
+ // - 32-bit network-byte-order: pid
+ // - 32-bit network-byte-order: signal number
+ // - byte: gwpAsanRecoverableCrash
+ // - bytes: raw text of the dump
+ // - null terminator
+ int headerBytes = readExactly(fd, buf, 0, 9);
+ if (headerBytes != 9) {
// protocol failure; give up
Slog.e(TAG, "Unable to read from debuggerd");
return;
@@ -217,69 +227,76 @@ final class NativeCrashListener extends Thread {
int pid = unpackInt(buf, 0);
int signal = unpackInt(buf, 4);
+ boolean gwpAsanRecoverableCrash = buf[8] != 0;
if (DEBUG) {
- Slog.v(TAG, "Read pid=" + pid + " signal=" + signal);
+ Slog.v(TAG, "Read pid=" + pid + " signal=" + signal
+ + " recoverable=" + gwpAsanRecoverableCrash);
+ }
+ if (pid < 0) {
+ Slog.e(TAG, "Bogus pid!");
+ return;
}
// now the text of the dump
- if (pid > 0) {
- final ProcessRecord pr;
- synchronized (mAm.mPidsSelfLocked) {
- pr = mAm.mPidsSelfLocked.get(pid);
+ final ProcessRecord pr;
+ synchronized (mAm.mPidsSelfLocked) {
+ pr = mAm.mPidsSelfLocked.get(pid);
+ }
+ if (pr == null) {
+ Slog.w(TAG, "Couldn't find ProcessRecord for pid " + pid);
+ return;
+ }
+
+ // Don't attempt crash reporting for persistent apps
+ if (pr.isPersistent()) {
+ if (DEBUG) {
+ Slog.v(TAG, "Skipping report for persistent app " + pr);
}
- if (pr != null) {
- // Don't attempt crash reporting for persistent apps
- if (pr.isPersistent()) {
- if (DEBUG) {
- Slog.v(TAG, "Skipping report for persistent app " + pr);
- }
- return;
- }
+ return;
+ }
- int bytes;
- do {
- // get some data
- bytes = Os.read(fd, buf, 0, buf.length);
- if (bytes > 0) {
- if (MORE_DEBUG) {
- String s = new String(buf, 0, bytes, "UTF-8");
- Slog.v(TAG, "READ=" + bytes + "> " + s);
- }
- // did we just get the EOD null byte?
- if (buf[bytes-1] == 0) {
- os.write(buf, 0, bytes-1); // exclude the EOD token
- break;
- }
- // no EOD, so collect it and read more
- os.write(buf, 0, bytes);
- }
- } while (bytes > 0);
-
- // Okay, we've got the report.
- if (DEBUG) Slog.v(TAG, "processing");
-
- // Mark the process record as being a native crash so that the
- // cleanup mechanism knows we're still submitting the report
- // even though the process will vanish as soon as we let
- // debuggerd proceed.
- synchronized (mAm) {
- synchronized (mAm.mProcLock) {
- pr.mErrorState.setCrashing(true);
- pr.mErrorState.setForceCrashReport(true);
- }
+ int bytes;
+ do {
+ // get some data
+ bytes = Os.read(fd, buf, 0, buf.length);
+ if (bytes > 0) {
+ if (MORE_DEBUG) {
+ String s = new String(buf, 0, bytes, "UTF-8");
+ Slog.v(TAG, "READ=" + bytes + "> " + s);
+ }
+ // did we just get the EOD null byte?
+ if (buf[bytes - 1] == 0) {
+ os.write(buf, 0, bytes - 1); // exclude the EOD token
+ break;
+ }
+ // no EOD, so collect it and read more
+ os.write(buf, 0, bytes);
+ }
+ } while (bytes > 0);
+
+ // Okay, we've got the report.
+ if (DEBUG) Slog.v(TAG, "processing");
+
+ // Mark the process record as being a native crash so that the
+ // cleanup mechanism knows we're still submitting the report even
+ // though the process will vanish as soon as we let debuggerd
+ // proceed. This isn't relevant for recoverable crashes, as we don't
+ // show the user an "app crashed" dialogue because the app (by
+ // design) didn't crash.
+ if (!gwpAsanRecoverableCrash) {
+ synchronized (mAm) {
+ synchronized (mAm.mProcLock) {
+ pr.mErrorState.setCrashing(true);
+ pr.mErrorState.setForceCrashReport(true);
}
-
- // Crash reporting is synchronous but we want to let debuggerd
- // go about it business right away, so we spin off the actual
- // reporting logic on a thread and let it take it's time.
- final String reportString = new String(os.toByteArray(), "UTF-8");
- (new NativeCrashReporter(pr, signal, reportString)).start();
- } else {
- Slog.w(TAG, "Couldn't find ProcessRecord for pid " + pid);
}
- } else {
- Slog.e(TAG, "Bogus pid!");
}
+
+ // Crash reporting is synchronous but we want to let debuggerd
+ // go about it business right away, so we spin off the actual
+ // reporting logic on a thread and let it take it's time.
+ final String reportString = new String(os.toByteArray(), "UTF-8");
+ (new NativeCrashReporter(pr, signal, gwpAsanRecoverableCrash, reportString)).start();
} catch (Exception e) {
Slog.e(TAG, "Exception dealing with report", e);
// ugh, fail.
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index b9c9efee0ffd..84a80993c4b2 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -3457,7 +3457,8 @@ public class OomAdjuster {
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
// if an app is already frozen and shouldNotFreeze becomes true, immediately unfreeze
if (opt.isFrozen() && opt.shouldNotFreeze()) {
- mCachedAppOptimizer.unfreezeAppLSP(app, oomAdjReason);
+ mCachedAppOptimizer.unfreezeAppLSP(app,
+ CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason));
return;
}
@@ -3467,7 +3468,8 @@ public class OomAdjuster {
&& !opt.shouldNotFreeze()) {
mCachedAppOptimizer.freezeAppAsyncLSP(app);
} else if (state.getSetAdj() < CACHED_APP_MIN_ADJ) {
- mCachedAppOptimizer.unfreezeAppLSP(app, oomAdjReason);
+ mCachedAppOptimizer.unfreezeAppLSP(app,
+ CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason));
}
}
}
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 1d48cb25f03a..e66894b596e9 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -22,7 +22,9 @@ import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
import static com.android.server.am.ActivityManagerService.MY_PID;
import static com.android.server.am.ProcessRecord.TAG;
+import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AnrController;
import android.app.ApplicationErrorReport;
@@ -56,6 +58,7 @@ import com.android.internal.os.anr.AnrLatencyTracker;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.ResourcePressureUtil;
import com.android.server.criticalevents.CriticalEventLog;
+import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
import com.android.server.wm.WindowProcessController;
import java.io.File;
@@ -396,6 +399,8 @@ class ProcessErrorStateRecord {
});
}
}
+ // Build memory headers for the ANRing process.
+ String memoryHeaders = buildMemoryHeadersFor(pid);
// Get critical event log before logging the ANR so that it doesn't occur in the log.
latencyTracker.criticalEventLogStarted();
@@ -496,7 +501,7 @@ class ProcessErrorStateRecord {
File tracesFile = StackTracesDumpHelper.dumpStackTraces(firstPids,
isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
nativePidsFuture, tracesFileException, firstPidEndOffset, annotation,
- criticalEventLog, auxiliaryTaskExecutor, latencyTracker);
+ criticalEventLog, memoryHeaders, auxiliaryTaskExecutor, latencyTracker);
if (isMonitorCpuUsage()) {
// Wait for the first call to finish
@@ -710,6 +715,26 @@ class ProcessErrorStateRecord {
resolver.getUserId()) != 0;
}
+ private @Nullable String buildMemoryHeadersFor(int pid) {
+ if (pid <= 0) {
+ Slog.i(TAG, "Memory header requested with invalid pid: " + pid);
+ return null;
+ }
+ MemorySnapshot snapshot = readMemorySnapshotFromProcfs(pid);
+ if (snapshot == null) {
+ Slog.i(TAG, "Failed to get memory snapshot for pid:" + pid);
+ return null;
+ }
+
+ StringBuilder memoryHeaders = new StringBuilder();
+ memoryHeaders.append("RssHwmKb: ")
+ .append(snapshot.rssHighWaterMarkInKilobytes)
+ .append("\n");
+ memoryHeaders.append("RssKb: ").append(snapshot.rssInKilobytes).append("\n");
+ memoryHeaders.append("RssAnonKb: ").append(snapshot.anonRssInKilobytes).append("\n");
+ memoryHeaders.append("VmSwapKb: ").append(snapshot.swapInKilobytes).append("\n");
+ return memoryHeaders.toString();
+ }
/**
* Unless configured otherwise, swallow ANRs in background processes & kill the process.
* Non-private access is for tests only.
diff --git a/services/core/java/com/android/server/am/StackTracesDumpHelper.java b/services/core/java/com/android/server/am/StackTracesDumpHelper.java
index 937332894dbd..10ddc2f562dc 100644
--- a/services/core/java/com/android/server/am/StackTracesDumpHelper.java
+++ b/services/core/java/com/android/server/am/StackTracesDumpHelper.java
@@ -85,7 +85,8 @@ public class StackTracesDumpHelper {
Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
@NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
- logExceptionCreatingFile, null, null, null, auxiliaryTaskExecutor, latencyTracker);
+ logExceptionCreatingFile, null, null, null, null, auxiliaryTaskExecutor,
+ latencyTracker);
}
/**
@@ -99,7 +100,7 @@ public class StackTracesDumpHelper {
AnrLatencyTracker latencyTracker) {
return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
logExceptionCreatingFile, null, subject, criticalEventSection,
- auxiliaryTaskExecutor, latencyTracker);
+ /* memoryHeaders= */ null, auxiliaryTaskExecutor, latencyTracker);
}
/**
@@ -110,7 +111,8 @@ public class StackTracesDumpHelper {
ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
AtomicLong firstPidEndOffset, String subject, String criticalEventSection,
- @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
+ String memoryHeaders, @NonNull Executor auxiliaryTaskExecutor,
+ AnrLatencyTracker latencyTracker) {
try {
if (latencyTracker != null) {
@@ -150,9 +152,10 @@ public class StackTracesDumpHelper {
return null;
}
- if (subject != null || criticalEventSection != null) {
+ if (subject != null || criticalEventSection != null || memoryHeaders != null) {
appendtoANRFile(tracesFile.getAbsolutePath(),
- (subject != null ? "Subject: " + subject + "\n\n" : "")
+ (subject != null ? "Subject: " + subject + "\n" : "")
+ + (memoryHeaders != null ? memoryHeaders + "\n\n" : "")
+ (criticalEventSection != null ? criticalEventSection : ""));
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 6758581d1fc0..43063afb4ce7 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1359,6 +1359,9 @@ public class AudioDeviceInventory {
"LE Audio device addr=" + address + " now available").printLog(TAG));
}
+ // Reset LEA suspend state each time a new sink is connected
+ mAudioSystem.setParameters("LeAudioSuspended=false");
+
mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
mDeviceBroker.postAccessoryPlugMediaUnmute(device);
@@ -1404,6 +1407,9 @@ public class AudioDeviceInventory {
@GuardedBy("mDevicesLock")
private void makeLeAudioDeviceUnavailableLater(String address, int device, int delayMs) {
+ // prevent any activity on the LEA output to avoid unwanted
+ // reconnection of the sink.
+ mAudioSystem.setParameters("LeAudioSuspended=true");
// the device will be made unavailable later, so consider it disconnected right away
mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
// send the delayed message to make the device unavailable later
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 3780620ca0d3..d43687be6128 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -929,7 +929,7 @@ public class AudioService extends IAudioService.Stub
// Defines the format for the connection "address" for ALSA devices
public static String makeAlsaAddressString(int card, int device) {
- return "card=" + card + ";device=" + device + ";";
+ return "card=" + card + ";device=" + device;
}
public static final class Lifecycle extends SystemService {
@@ -4424,13 +4424,14 @@ public class AudioService extends IAudioService.Stub
return;
}
- // Forcefully set LE audio volume as a workaround, since in some cases
- // (like the outgoing call) the value of 'device' is not DEVICE_OUT_BLE_*
- // even when BLE is connected.
+ // In some cases (like the outgoing or rejected call) the value of 'device' is not
+ // DEVICE_OUT_BLE_* even when BLE is connected. Changing the volume level in such case
+ // may cuase the other devices volume level leaking into the LeAudio device settings.
if (!AudioSystem.isLeAudioDeviceType(device)) {
- Log.w(TAG, "setLeAudioVolumeOnModeUpdate got unexpected device=" + device
- + ", forcing to device=" + AudioSystem.DEVICE_OUT_BLE_HEADSET);
- device = AudioSystem.DEVICE_OUT_BLE_HEADSET;
+ Log.w(TAG, "setLeAudioVolumeOnModeUpdate ignoring invalid device="
+ + device + ", mode=" + mode + ", index=" + index + " maxIndex=" + maxIndex
+ + " streamType=" + streamType);
+ return;
}
if (DEBUG_VOL) {
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 2dcdc5419452..631d7f5a170d 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -493,6 +493,7 @@ public class BtHelper {
mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
AudioSystem.setParameters("A2dpSuspended=false");
+ AudioSystem.setParameters("LeAudioSuspended=false");
mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index e48412ab4029..82b4da3850f4 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -103,6 +103,7 @@ import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
+import android.util.Pair;
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
@@ -1396,11 +1397,14 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
Log.v(TAG, "SV count: " + gnssStatus.getSatelliteCount());
}
+ Set<Pair<Integer, Integer>> satellites = new HashSet<>();
int usedInFixCount = 0;
int maxCn0 = 0;
int meanCn0 = 0;
for (int i = 0; i < gnssStatus.getSatelliteCount(); i++) {
if (gnssStatus.usedInFix(i)) {
+ satellites.add(
+ new Pair<>(gnssStatus.getConstellationType(i), gnssStatus.getSvid(i)));
++usedInFixCount;
if (gnssStatus.getCn0DbHz(i) > maxCn0) {
maxCn0 = (int) gnssStatus.getCn0DbHz(i);
@@ -1413,7 +1417,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
meanCn0 /= usedInFixCount;
}
// return number of sats used in fix instead of total reported
- mLocationExtras.set(usedInFixCount, meanCn0, maxCn0);
+ mLocationExtras.set(satellites.size(), meanCn0, maxCn0);
mGnssMetrics.logSvStatus(gnssStatus);
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 1cfcb4ea3a7e..c9a6c630d41b 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -545,6 +545,7 @@ public final class NotificationRecord {
pw.println(prefix + "mAdjustments=" + mAdjustments);
pw.println(prefix + "shortcut=" + notification.getShortcutId()
+ " found valid? " + (mShortcutInfo != null));
+ pw.println(prefix + "mUserVisOverride=" + getPackageVisibilityOverride());
}
private void dumpNotification(PrintWriter pw, String prefix, Notification notification,
@@ -574,6 +575,7 @@ public final class NotificationRecord {
} else {
pw.println("null");
}
+ pw.println(prefix + "vis=" + notification.visibility);
pw.println(prefix + "contentView=" + formatRemoteViews(notification.contentView));
pw.println(prefix + "bigContentView=" + formatRemoteViews(notification.bigContentView));
pw.println(prefix + "headsUpContentView="
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2038e798a038..6b213b78f11c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -7208,6 +7208,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
* TODO: In the meantime, can this be moved to a schedule call?
* TODO(b/182523293): This should be removed once we finish migration of permission storage.
*/
+ @SuppressWarnings("GuardedBy")
void writeSettingsLPrTEMP(boolean sync) {
snapshotComputer(false);
mPermissionManager.writeLegacyPermissionsTEMP(mSettings.mPermissions);
@@ -7257,6 +7258,10 @@ public class PackageManagerService implements PackageSender, TestUtilityService
static boolean isPreapprovalRequestAvailable() {
final long token = Binder.clearCallingIdentity();
try {
+ if (!Resources.getSystem().getBoolean(
+ com.android.internal.R.bool.config_isPreApprovalRequestAvailable)) {
+ return false;
+ }
return DeviceConfig.getBoolean(NAMESPACE_PACKAGE_MANAGER_SERVICE,
PROPERTY_IS_PRE_APPROVAL_REQUEST_AVAILABLE, true /* defaultValue */);
} finally {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 77e4688cecb5..42538f33c5f8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -42,7 +42,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
+import android.compat.annotation.Disabled;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -189,7 +189,7 @@ public class PackageManagerServiceUtils {
* allow 3P apps to trigger internal-only functionality.
*/
@ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ @Disabled /* Revert enforcement: b/274147456 */
private static final long ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = 161252188;
/**
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 02d13bcbb8ae..417ba0729066 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2217,10 +2217,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
}
if (ustate.getEnabledState() != COMPONENT_ENABLED_STATE_DEFAULT) {
serializer.attributeInt(null, ATTR_ENABLED, ustate.getEnabledState());
- if (ustate.getLastDisableAppCaller() != null) {
- serializer.attribute(null, ATTR_ENABLED_CALLER,
- ustate.getLastDisableAppCaller());
- }
+ }
+ if (ustate.getLastDisableAppCaller() != null) {
+ serializer.attribute(null, ATTR_ENABLED_CALLER,
+ ustate.getLastDisableAppCaller());
}
if (ustate.getInstallReason() != PackageManager.INSTALL_REASON_UNKNOWN) {
serializer.attributeInt(null, ATTR_INSTALL_REASON,
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index 135841729e69..2638f34fe7df 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -360,7 +360,7 @@ public class PowerStatsService extends SystemService {
sb.append("ALL");
}
sb.append("[");
- for (int i = 0; i < expectedLength; i++) {
+ for (int i = 0; i < energyConsumerIds.length; i++) {
final int id = energyConsumerIds[i];
sb.append(id);
sb.append("(type:");
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
index 2141eba3be50..7f129ea3801c 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
@@ -171,6 +171,18 @@ class NetworkPriorityClassifier {
return false;
}
+ for (Map.Entry<Integer, Integer> entry :
+ networkPriority.getCapabilitiesMatchCriteria().entrySet()) {
+ final int cap = entry.getKey();
+ final int matchCriteria = entry.getValue();
+
+ if (matchCriteria == MATCH_REQUIRED && !caps.hasCapability(cap)) {
+ return false;
+ } else if (matchCriteria == MATCH_FORBIDDEN && caps.hasCapability(cap)) {
+ return false;
+ }
+ }
+
if (vcnContext.isInTestMode() && caps.hasTransport(TRANSPORT_TEST)) {
return true;
}
@@ -319,18 +331,6 @@ class NetworkPriorityClassifier {
return false;
}
- for (Map.Entry<Integer, Integer> entry :
- networkPriority.getCapabilitiesMatchCriteria().entrySet()) {
- final int cap = entry.getKey();
- final int matchCriteria = entry.getValue();
-
- if (matchCriteria == MATCH_REQUIRED && !caps.hasCapability(cap)) {
- return false;
- } else if (matchCriteria == MATCH_FORBIDDEN && caps.hasCapability(cap)) {
- return false;
- }
- }
-
return true;
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 81dabfd48bf3..94e041aa3937 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6046,6 +6046,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// An activity must be in the {@link PAUSING} state for the system to validate
// the move to {@link PAUSED}.
setState(PAUSING, "makeActiveIfNeeded");
+ EventLogTags.writeWmPauseActivity(mUserId, System.identityHashCode(this),
+ shortComponentName, "userLeaving=false", "make-active");
try {
mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
PauseActivityItem.obtain(finishing, false /* userLeaving */,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index bbdaa24a694c..12fe6a0dba25 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -100,6 +100,7 @@ import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_LAST_OR
import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_FIRST_ORDERED_ID;
import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_LAST_ORDERED_ID;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ROOT_TASK;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
@@ -2011,7 +2012,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
return;
}
- if (r == mRootWindowContainer.getTopResumedActivity()) {
+ if (r.isState(RESUMED) && r == mRootWindowContainer.getTopResumedActivity()) {
setLastResumedActivityUncheckLocked(r, "setFocusedTask-alreadyTop");
return;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index feaec37fb985..be503fc61c4c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1562,7 +1562,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
private void removePinnedRootTaskInSurfaceTransaction(Task rootTask) {
- rootTask.mTransitionController.requestCloseTransitionIfNeeded(rootTask);
/**
* Workaround: Force-stop all the activities in the root pinned task before we reparent them
* to the fullscreen root task. This is to guarantee that when we are removing a root task,
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a44f25ca8051..89cb13a40f8d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5153,12 +5153,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
@Override
void updateAboveInsetsState(InsetsState aboveInsetsState,
- SparseArray<InsetsSourceProvider> localInsetsSourceProvidersFromParent,
+ SparseArray<InsetsSource> localInsetsSourcesFromParent,
ArraySet<WindowState> insetsChangedWindows) {
if (skipImeWindowsDuringTraversal(mDisplayContent)) {
return;
}
- super.updateAboveInsetsState(aboveInsetsState, localInsetsSourceProvidersFromParent,
+ super.updateAboveInsetsState(aboveInsetsState, localInsetsSourcesFromParent,
insetsChangedWindows);
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 72263ffc7de2..6af1c7c9d656 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -87,8 +87,6 @@ import java.util.Set;
*/
public class DisplayRotation {
private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayRotation" : TAG_WM;
- // Delay to avoid race between fold update and orientation update.
- private static final int ORIENTATION_UPDATE_DELAY_MS = 800;
// Delay in milliseconds when updating config due to folding events. This prevents
// config changes and unexpected jumps while folding the device to closed state.
@@ -1789,15 +1787,15 @@ public class DisplayRotation {
mDeviceState = newState;
// Now mFoldState is set to HALF_FOLDED, the overrideFrozenRotation function will
// return true, so rotation is unlocked.
+ mService.updateRotation(false /* alwaysSendConfiguration */,
+ false /* forceRelayout */);
} else {
mInHalfFoldTransition = true;
mDeviceState = newState;
+ // Tell the device to update its orientation.
+ mService.updateRotation(false /* alwaysSendConfiguration */,
+ false /* forceRelayout */);
}
- UiThread.getHandler().postDelayed(
- () -> {
- mService.updateRotation(false /* alwaysSendConfiguration */,
- false /* forceRelayout */);
- }, ORIENTATION_UPDATE_DELAY_MS);
// Alert the activity of possible new bounds.
UiThread.getHandler().removeCallbacks(mActivityBoundsUpdateCallback);
UiThread.getHandler().postDelayed(mActivityBoundsUpdateCallback,
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 4be98a3c88b7..b4dffdcba243 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -48,7 +48,7 @@ import java.io.PrintWriter;
* Controller for IME inset source on the server. It's called provider as it provides the
* {@link InsetsSource} to the client that uses it in {@link InsetsSourceConsumer}.
*/
-final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider {
+final class ImeInsetsSourceProvider extends InsetsSourceProvider {
/** The token tracking the current IME request or {@code null} otherwise. */
@Nullable
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index a8c9cd30b656..fe13b87a079a 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -223,10 +223,10 @@ class InsetsPolicy {
startAnimation(false /* show */, () -> {
synchronized (mDisplayContent.mWmService.mGlobalLock) {
- final SparseArray<WindowContainerInsetsSourceProvider> providers =
+ final SparseArray<InsetsSourceProvider> providers =
mStateController.getSourceProviders();
for (int i = providers.size() - 1; i >= 0; i--) {
- final WindowContainerInsetsSourceProvider provider = providers.valueAt(i);
+ final InsetsSourceProvider provider = providers.valueAt(i);
if (!isTransient(provider.getSource().getType())) {
continue;
}
@@ -341,11 +341,10 @@ class InsetsPolicy {
}
}
- final SparseArray<WindowContainerInsetsSourceProvider> providers =
- mStateController.getSourceProviders();
+ final SparseArray<InsetsSourceProvider> providers = mStateController.getSourceProviders();
final int windowType = attrs.type;
for (int i = providers.size() - 1; i >= 0; i--) {
- final WindowContainerInsetsSourceProvider otherProvider = providers.valueAt(i);
+ final InsetsSourceProvider otherProvider = providers.valueAt(i);
if (otherProvider.overridesFrame(windowType)) {
if (state == originalState) {
state = new InsetsState(state);
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 0953604511d7..3b23f9717175 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -58,7 +58,7 @@ import java.util.function.Consumer;
* Controller for a specific inset source on the server. It's called provider as it provides the
* {@link InsetsSource} to the client that uses it in {@link android.view.InsetsSourceConsumer}.
*/
-abstract class InsetsSourceProvider {
+class InsetsSourceProvider {
protected final DisplayContent mDisplayContent;
protected final @NonNull InsetsSource mSource;
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index e4ffb8de46e0..249ead0a8509 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -56,7 +56,7 @@ class InsetsStateController {
private final InsetsState mState = new InsetsState();
private final DisplayContent mDisplayContent;
- private final SparseArray<WindowContainerInsetsSourceProvider> mProviders = new SparseArray<>();
+ private final SparseArray<InsetsSourceProvider> mProviders = new SparseArray<>();
private final ArrayMap<InsetsControlTarget, ArrayList<InsetsSourceProvider>>
mControlTargetProvidersMap = new ArrayMap<>();
private final SparseArray<InsetsControlTarget> mIdControlTargetMap = new SparseArray<>();
@@ -106,22 +106,22 @@ class InsetsStateController {
return result;
}
- SparseArray<WindowContainerInsetsSourceProvider> getSourceProviders() {
+ SparseArray<InsetsSourceProvider> getSourceProviders() {
return mProviders;
}
/**
* @return The provider of a specific source ID.
*/
- WindowContainerInsetsSourceProvider getOrCreateSourceProvider(int id, @InsetsType int type) {
- WindowContainerInsetsSourceProvider provider = mProviders.get(id);
+ InsetsSourceProvider getOrCreateSourceProvider(int id, @InsetsType int type) {
+ InsetsSourceProvider provider = mProviders.get(id);
if (provider != null) {
return provider;
}
final InsetsSource source = mState.getOrCreateSource(id, type);
provider = id == ID_IME
? new ImeInsetsSourceProvider(source, this, mDisplayContent)
- : new WindowContainerInsetsSourceProvider(source, this, mDisplayContent);
+ : new InsetsSourceProvider(source, this, mDisplayContent);
mProviders.put(id, provider);
return provider;
}
@@ -161,14 +161,15 @@ class InsetsStateController {
final InsetsState aboveInsetsState = new InsetsState();
aboveInsetsState.set(mState,
displayCutout() | systemGestures() | mandatorySystemGestures());
+ final SparseArray<InsetsSource> localInsetsSourcesFromParent = new SparseArray<>();
final ArraySet<WindowState> insetsChangedWindows = new ArraySet<>();
- final SparseArray<InsetsSourceProvider>
- localInsetsSourceProvidersFromParent = new SparseArray<>();
+
// This method will iterate on the entire hierarchy in top to bottom z-order manner. The
// aboveInsetsState will be modified as per the insets provided by the WindowState being
// visited.
- mDisplayContent.updateAboveInsetsState(aboveInsetsState,
- localInsetsSourceProvidersFromParent, insetsChangedWindows);
+ mDisplayContent.updateAboveInsetsState(aboveInsetsState, localInsetsSourcesFromParent,
+ insetsChangedWindows);
+
if (notifyInsetsChange) {
for (int i = insetsChangedWindows.size() - 1; i >= 0; i--) {
mDispatchInsetsChanged.accept(insetsChangedWindows.valueAt(i));
@@ -333,7 +334,7 @@ class InsetsStateController {
}
mDisplayContent.mWmService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
for (int i = mProviders.size() - 1; i >= 0; i--) {
- final WindowContainerInsetsSourceProvider provider = mProviders.valueAt(i);
+ final InsetsSourceProvider provider = mProviders.valueAt(i);
provider.onSurfaceTransactionApplied();
}
final ArraySet<InsetsControlTarget> newControlTargets = new ArraySet<>();
diff --git a/services/core/java/com/android/server/wm/RectInsetsSourceProvider.java b/services/core/java/com/android/server/wm/RectInsetsSourceProvider.java
deleted file mode 100644
index 6e8beee86576..000000000000
--- a/services/core/java/com/android/server/wm/RectInsetsSourceProvider.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import android.graphics.Rect;
-import android.util.Slog;
-import android.view.InsetsSource;
-
-/**
- * An {@link InsetsSourceProvider} which doesn't have a backing window or a window container.
- */
-public class RectInsetsSourceProvider extends InsetsSourceProvider {
- private static final String TAG = TAG_WITH_CLASS_NAME
- ? RectInsetsSourceProvider.class.getSimpleName()
- : TAG_WM;
-
- RectInsetsSourceProvider(InsetsSource source,
- InsetsStateController stateController, DisplayContent displayContent) {
- super(source, stateController, displayContent);
- }
-
- /**
- * Sets the given {@code rect} as the frame of the underlying {@link InsetsSource}.
- */
- void setRect(Rect rect) {
- mSource.setFrame(rect);
- mSource.setVisible(true);
- }
-
- @Override
- void onPostLayout() {
- if (WindowManagerDebugConfig.DEBUG) {
- Slog.d(TAG, "onPostLayout(), not calling super.onPostLayout().");
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 683767e474ef..c6ba60026cdf 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -783,7 +783,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
* a chance we won't thus legacy-entry (via pause+userLeaving) will return false.
*/
private boolean checkEnterPipOnFinish(@NonNull ActivityRecord ar) {
- if (!mCanPipOnFinish || !ar.isVisible() || ar.getTask() == null) return false;
+ if (!mCanPipOnFinish || !ar.isVisible() || ar.getTask() == null || !ar.isState(RESUMED)) {
+ return false;
+ }
if (ar.pictureInPictureArgs != null && ar.pictureInPictureArgs.isAutoEnterEnabled()) {
if (didCommitTransientLaunch()) {
@@ -796,18 +798,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
// Legacy pip-entry (not via isAutoEnterEnabled).
- boolean canPip = ar.getDeferHidingClient();
- if (!canPip && didCommitTransientLaunch()) {
+ if (didCommitTransientLaunch() && ar.supportsPictureInPicture()) {
// force enable pip-on-task-switch now that we've committed to actually launching to the
// transient activity, and then recalculate whether we can attempt pip.
ar.supportsEnterPipOnTaskSwitch = true;
- canPip = ar.checkEnterPictureInPictureState(
- "finishTransition", true /* beforeStopping */)
- && ar.isState(RESUMED);
}
- if (!canPip) return false;
+
try {
- // Legacy PIP-enter requires pause event with user-leaving.
+ // If not going auto-pip, the activity should be paused with user-leaving.
mController.mAtm.mTaskSupervisor.mUserLeaving = true;
ar.getTaskFragment().startPausing(false /* uiSleeping */,
null /* resuming */, "finishTransition");
@@ -851,6 +849,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
boolean hasParticipatedDisplay = false;
boolean hasVisibleTransientLaunch = false;
+ boolean enterAutoPip = false;
// Commit all going-invisible containers
for (int i = 0; i < mParticipants.size(); ++i) {
final WindowContainer<?> participant = mParticipants.valueAt(i);
@@ -886,6 +885,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
ar.commitVisibility(false /* visible */, false /* performLayout */,
true /* fromTransition */);
+ } else {
+ enterAutoPip = true;
}
}
if (mChanges.get(ar).mVisible != visibleAtTransitionEnd) {
@@ -940,8 +941,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
if (hasVisibleTransientLaunch) {
- // Notify the change about the transient-below task that becomes invisible.
- mController.mAtm.getTaskChangeNotificationController().notifyTaskStackChanged();
+ // Notify the change about the transient-below task if entering auto-pip.
+ if (enterAutoPip) {
+ mController.mAtm.getTaskChangeNotificationController().notifyTaskStackChanged();
+ }
// Prevent spurious background app switches.
mController.mAtm.stopAppSwitches();
// The end of transient launch may not reorder task, so make sure to compute the latest
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index bd0344faa078..41176410a789 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -157,15 +157,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
boolean mReparenting;
/**
- * Map of {@link InsetsState.InternalInsetsType} to the {@link InsetsSourceProvider} that
- * provides local insets for all children of the current {@link WindowContainer}.
- *
- * Note that these InsetsSourceProviders are not part of the {@link InsetsStateController} and
- * live here. These are supposed to provide insets only to the subtree of the current
+ * Map of the source ID to the {@link InsetsSource} for all children of the current
* {@link WindowContainer}.
+ *
+ * Note that these sources are not part of the {@link InsetsStateController} and live here.
+ * These are supposed to provide insets only to the subtree of this {@link WindowContainer}.
*/
@Nullable
- SparseArray<InsetsSourceProvider> mLocalInsetsSourceProviders = null;
+ SparseArray<InsetsSource> mLocalInsetsSources = null;
@Nullable
protected InsetsSourceProvider mControllableInsetProvider;
@@ -374,49 +373,46 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
* {@link WindowState}s below it.
*
* {@link WindowState#mMergedLocalInsetsSources} is updated by considering
- * {@link WindowContainer#mLocalInsetsSourceProviders} provided by all the parents of the
- * window.
- * A given insetsType can be provided as a LocalInsetsSourceProvider only once in a
- * Parent-to-leaf path.
+ * {@link WindowContainer#mLocalInsetsSources} provided by all the parents of the window.
*
* Examples: Please take a look at
* {@link WindowContainerTests#testAddLocalInsetsSourceProvider()}
- * {@link
- * WindowContainerTests#testAddLocalInsetsSourceProvider_windowSkippedIfProvidingOnParent()}
* {@link WindowContainerTests#testRemoveLocalInsetsSourceProvider()}.
*
- * @param aboveInsetsState The InsetsState of all the Windows above the current container.
- * @param localInsetsSourceProvidersFromParent The local InsetsSourceProviders provided by all
- * the parents in the hierarchy of the current
- * container.
- * @param insetsChangedWindows The windows which the insets changed have changed for.
+ * @param aboveInsetsState The InsetsState of all the Windows above the current
+ * container.
+ * @param localInsetsSourcesFromParent The local InsetsSourceProviders provided by all
+ * the parents in the hierarchy of the current
+ * container.
+ * @param insetsChangedWindows The windows which the insets changed have changed for.
*/
void updateAboveInsetsState(InsetsState aboveInsetsState,
- SparseArray<InsetsSourceProvider> localInsetsSourceProvidersFromParent,
+ SparseArray<InsetsSource> localInsetsSourcesFromParent,
ArraySet<WindowState> insetsChangedWindows) {
- SparseArray<InsetsSourceProvider> mergedLocalInsetsSourceProviders =
- localInsetsSourceProvidersFromParent;
- if (mLocalInsetsSourceProviders != null && mLocalInsetsSourceProviders.size() != 0) {
- mergedLocalInsetsSourceProviders = createShallowCopy(mergedLocalInsetsSourceProviders);
- for (int i = 0; i < mLocalInsetsSourceProviders.size(); i++) {
- mergedLocalInsetsSourceProviders.put(
- mLocalInsetsSourceProviders.keyAt(i),
- mLocalInsetsSourceProviders.valueAt(i));
- }
- }
+ final SparseArray<InsetsSource> mergedLocalInsetsSources =
+ createMergedSparseArray(localInsetsSourcesFromParent, mLocalInsetsSources);
for (int i = mChildren.size() - 1; i >= 0; --i) {
- mChildren.get(i).updateAboveInsetsState(aboveInsetsState,
- mergedLocalInsetsSourceProviders, insetsChangedWindows);
+ mChildren.get(i).updateAboveInsetsState(aboveInsetsState, mergedLocalInsetsSources,
+ insetsChangedWindows);
}
}
- static <T> SparseArray<T> createShallowCopy(SparseArray<T> inputArray) {
- SparseArray<T> copyOfInput = new SparseArray<>(inputArray.size());
- for (int i = 0; i < inputArray.size(); i++) {
- copyOfInput.append(inputArray.keyAt(i), inputArray.valueAt(i));
+ static <T> SparseArray<T> createMergedSparseArray(SparseArray<T> sa1, SparseArray<T> sa2) {
+ final int size1 = sa1 != null ? sa1.size() : 0;
+ final int size2 = sa2 != null ? sa2.size() : 0;
+ final SparseArray<T> mergedArray = new SparseArray<>(size1 + size2);
+ if (size1 > 0) {
+ for (int i = 0; i < size1; i++) {
+ mergedArray.append(sa1.keyAt(i), sa1.valueAt(i));
+ }
+ }
+ if (size2 > 0) {
+ for (int i = 0; i < size2; i++) {
+ mergedArray.put(sa2.keyAt(i), sa2.valueAt(i));
+ }
}
- return copyOfInput;
+ return mergedArray;
}
/**
@@ -433,25 +429,23 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
// This is possible this container is detached when WM shell is responding to a previous
// request. WM shell will be updated when this container is attached again and the
// insets need to be updated.
- Slog.w(TAG, "Can't add local rect insets source provider when detached. " + this);
+ Slog.w(TAG, "Can't add insets frame provider when detached. " + this);
return;
}
- if (mLocalInsetsSourceProviders == null) {
- mLocalInsetsSourceProviders = new SparseArray<>();
+ if (mLocalInsetsSources == null) {
+ mLocalInsetsSources = new SparseArray<>();
}
final int id = InsetsSource.createId(
provider.getOwner(), provider.getIndex(), provider.getType());
- if (mLocalInsetsSourceProviders.get(id) != null) {
+ if (mLocalInsetsSources.get(id) != null) {
if (DEBUG) {
- Slog.d(TAG, "The local insets provider for this " + provider
- + " already exists. Overwriting");
+ Slog.d(TAG, "The local insets source for this " + provider
+ + " already exists. Overwriting.");
}
}
- final RectInsetsSourceProvider insetsSourceProvider = new RectInsetsSourceProvider(
- new InsetsSource(id, provider.getType()),
- mDisplayContent.getInsetsStateController(), mDisplayContent);
- mLocalInsetsSourceProviders.put(id, insetsSourceProvider);
- insetsSourceProvider.setRect(provider.getArbitraryRectangle());
+ final InsetsSource source = new InsetsSource(id, provider.getType());
+ source.setFrame(provider.getArbitraryRectangle());
+ mLocalInsetsSources.put(id, source);
mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
}
@@ -459,20 +453,19 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
if (provider == null) {
throw new IllegalArgumentException("Insets type not specified.");
}
- if (mLocalInsetsSourceProviders == null) {
+ if (mLocalInsetsSources == null) {
return;
}
final int id = InsetsSource.createId(
provider.getOwner(), provider.getIndex(), provider.getType());
- if (mLocalInsetsSourceProviders.get(id) == null) {
+ if (mLocalInsetsSources.get(id) == null) {
if (DEBUG) {
- Slog.d(TAG, "Given " + provider
- + " doesn't have a local insetsSourceProvider.");
+ Slog.d(TAG, "Given " + provider + " doesn't have a local insets source.");
}
return;
}
- mLocalInsetsSourceProviders.remove(id);
+ mLocalInsetsSources.remove(id);
// Update insets if this window is attached.
if (mDisplayContent != null) {
@@ -1014,8 +1007,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
if (dc != null && dc != this) {
dc.getPendingTransaction().merge(mPendingTransaction);
}
- if (dc != this && mLocalInsetsSourceProviders != null) {
- mLocalInsetsSourceProviders.clear();
+ if (dc != this && mLocalInsetsSources != null) {
+ mLocalInsetsSources.clear();
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer child = mChildren.get(i);
@@ -3555,11 +3548,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
pw.println(prefix + "mLastOrientationSource=" + mLastOrientationSource);
pw.println(prefix + "deepestLastOrientationSource=" + getLastOrientationSource());
}
- if (mLocalInsetsSourceProviders != null && mLocalInsetsSourceProviders.size() != 0) {
- pw.println(prefix + mLocalInsetsSourceProviders.size() + " LocalInsetsSourceProviders");
+ if (mLocalInsetsSources != null && mLocalInsetsSources.size() != 0) {
+ pw.println(prefix + mLocalInsetsSources.size() + " LocalInsetsSources");
final String childPrefix = prefix + " ";
- for (int i = 0; i < mLocalInsetsSourceProviders.size(); ++i) {
- mLocalInsetsSourceProviders.valueAt(i).dump(pw, childPrefix);
+ for (int i = 0; i < mLocalInsetsSources.size(); ++i) {
+ mLocalInsetsSources.valueAt(i).dump(childPrefix, pw);
}
}
}
@@ -4129,7 +4122,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
private void hideInsetSourceViewOverflows() {
- final SparseArray<WindowContainerInsetsSourceProvider> providers =
+ final SparseArray<InsetsSourceProvider> providers =
getDisplayContent().getInsetsStateController().getSourceProviders();
for (int i = providers.size(); i >= 0; i--) {
final InsetsSourceProvider insetProvider = providers.valueAt(i);
diff --git a/services/core/java/com/android/server/wm/WindowContainerInsetsSourceProvider.java b/services/core/java/com/android/server/wm/WindowContainerInsetsSourceProvider.java
deleted file mode 100644
index aa2e8f541058..000000000000
--- a/services/core/java/com/android/server/wm/WindowContainerInsetsSourceProvider.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import android.view.InsetsSource;
-
-/**
- * Controller for a specific inset source on the server. It's called provider as it provides the
- * {@link InsetsSource} to the client that uses it in {@link android.view.InsetsSourceConsumer}.
- */
-class WindowContainerInsetsSourceProvider extends InsetsSourceProvider {
- // TODO(b/218734524): Move the window container specific stuff from InsetsSourceProvider to
- // this class.
-
- WindowContainerInsetsSourceProvider(InsetsSource source,
- InsetsStateController stateController, DisplayContent displayContent) {
- super(source, stateController, displayContent);
- }
-}
-
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 232b817b8314..680f6052f36a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4495,20 +4495,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
@Override
void updateAboveInsetsState(InsetsState aboveInsetsState,
- SparseArray<InsetsSourceProvider> localInsetsSourceProvidersFromParent,
+ SparseArray<InsetsSource> localInsetsSourcesFromParent,
ArraySet<WindowState> insetsChangedWindows) {
- SparseArray<InsetsSourceProvider> mergedLocalInsetsSourceProviders =
- localInsetsSourceProvidersFromParent;
- if (mLocalInsetsSourceProviders != null && mLocalInsetsSourceProviders.size() != 0) {
- mergedLocalInsetsSourceProviders = createShallowCopy(mergedLocalInsetsSourceProviders);
- for (int i = 0; i < mLocalInsetsSourceProviders.size(); i++) {
- mergedLocalInsetsSourceProviders.put(
- mLocalInsetsSourceProviders.keyAt(i),
- mLocalInsetsSourceProviders.valueAt(i));
- }
- }
- final SparseArray<InsetsSource> mergedLocalInsetsSourcesFromParent =
- toInsetsSources(mergedLocalInsetsSourceProviders);
+ final SparseArray<InsetsSource> mergedLocalInsetsSources =
+ createMergedSparseArray(localInsetsSourcesFromParent, mLocalInsetsSources);
// Insets provided by the IME window can effect all the windows below it and hence it needs
// to be visited in the correct order. Because of which updateAboveInsetsState() can't be
@@ -4519,9 +4509,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
insetsChangedWindows.add(w);
}
- if (!mergedLocalInsetsSourcesFromParent.contentEquals(w.mMergedLocalInsetsSources)) {
- w.mMergedLocalInsetsSources = createShallowCopy(
- mergedLocalInsetsSourcesFromParent);
+ if (!mergedLocalInsetsSources.contentEquals(w.mMergedLocalInsetsSources)) {
+ w.mMergedLocalInsetsSources = mergedLocalInsetsSources;
insetsChangedWindows.add(w);
}
@@ -4534,17 +4523,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}, true /* traverseTopToBottom */);
}
- private static SparseArray<InsetsSource> toInsetsSources(
- SparseArray<InsetsSourceProvider> insetsSourceProviders) {
- final SparseArray<InsetsSource> insetsSources = new SparseArray<>(
- insetsSourceProviders.size());
- for (int i = 0; i < insetsSourceProviders.size(); i++) {
- insetsSources.append(insetsSourceProviders.keyAt(i),
- insetsSourceProviders.valueAt(i).getSource());
- }
- return insetsSources;
- }
-
private boolean forAllWindowTopToBottom(ToBooleanFunction<WindowState> callback) {
// We want to consume the positive sublayer children first because they need to appear
// above the parent, then this window (the parent), and then the negative sublayer children
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index 6bfcd39f948e..dce7b87c0328 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -72,7 +72,7 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta
@Override // from provider session
public void onProviderStatusChanged(ProviderSession.Status status,
- ComponentName componentName) {
+ ComponentName componentName, ProviderSession.CredentialsSource source) {
Log.i(TAG, "in onStatusChanged with status: " + status);
if (ProviderSession.isTerminatingStatus(status)) {
Log.i(TAG, "in onStatusChanged terminating status");
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index dfd8cfa12d6f..98dc8ab8aa9c 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -156,7 +156,7 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
@Override
public void onProviderStatusChanged(ProviderSession.Status status,
- ComponentName componentName) {
+ ComponentName componentName, ProviderSession.CredentialsSource source) {
Log.i(TAG, "in onProviderStatusChanged with status: " + status);
// If all provider responses have been received, we can either need the UI,
// or we need to respond with error. The only other case is the entry being
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 531a6bdc0130..de06d440fa9d 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -438,16 +438,12 @@ public final class CredentialManagerService
+ callingPackage);
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
- if (request.getOrigin() != null) {
- // Check privileged permissions
- mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
- }
- enforcePermissionForAllowedProviders(request);
-
final int userId = UserHandle.getCallingUserId();
final int callingUid = Binder.getCallingUid();
enforceCallingPackage(callingPackage, callingUid);
+ validateGetCredentialRequest(request);
+
// New request session, scoped for this request only.
final GetRequestSession session =
new GetRequestSession(
@@ -460,7 +456,24 @@ public final class CredentialManagerService
CancellationSignal.fromTransport(cancelTransport),
timestampBegan);
- processGetCredential(request, callback, session);
+ List<ProviderSession> providerSessions =
+ prepareProviderSessions(request, session);
+
+ if (providerSessions.isEmpty()) {
+ try {
+ callback.onError(
+ GetCredentialException.TYPE_NO_CREDENTIAL,
+ "No credentials available on this device.");
+ } catch (RemoteException e) {
+ Log.i(
+ TAG,
+ "Issue invoking onError on IGetCredentialCallback "
+ + "callback: "
+ + e.getMessage());
+ }
+ }
+
+ invokeProviderSessions(providerSessions);
return cancelTransport;
}
@@ -488,75 +501,22 @@ public final class CredentialManagerService
getContext(),
userId,
callingUid,
- prepareGetCredentialCallback,
getCredentialCallback,
request,
constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
CancellationSignal.fromTransport(cancelTransport),
- timestampBegan);
-
- processGetCredential(request, prepareGetCredentialCallback, session);
+ timestampBegan,
+ prepareGetCredentialCallback);
- return cancelTransport;
- }
-
- private void processGetCredential(
- GetCredentialRequest request,
- IPrepareGetCredentialCallback callback,
- PrepareGetRequestSession session) {
- List<ProviderSession> providerSessions;
-
- if (isCredentialDescriptionApiEnabled()) {
- List<CredentialOption> optionsThatRequireActiveCredentials =
- request.getCredentialOptions().stream()
- .filter(credentialOption -> credentialOption
- .getCredentialRetrievalData()
- .getStringArrayList(
- CredentialOption
- .SUPPORTED_ELEMENT_KEYS) != null)
- .toList();
-
- List<CredentialOption> optionsThatDoNotRequireActiveCredentials =
- request.getCredentialOptions().stream()
- .filter(credentialOption -> credentialOption
- .getCredentialRetrievalData()
- .getStringArrayList(
- CredentialOption
- .SUPPORTED_ELEMENT_KEYS) == null)
- .toList();
-
- List<ProviderSession> sessionsWithoutRemoteService =
- initiateProviderSessionsWithActiveContainers(
- session,
- getFilteredResultFromRegistry(optionsThatRequireActiveCredentials));
-
- List<ProviderSession> sessionsWithRemoteService =
- initiateProviderSessions(
- session,
- optionsThatDoNotRequireActiveCredentials.stream()
- .map(CredentialOption::getType)
- .collect(Collectors.toList()));
-
- Set<ProviderSession> all = new LinkedHashSet<>();
- all.addAll(sessionsWithRemoteService);
- all.addAll(sessionsWithoutRemoteService);
-
- providerSessions = new ArrayList<>(all);
- } else {
- // Initiate all provider sessions
- providerSessions =
- initiateProviderSessions(
- session,
- request.getCredentialOptions().stream()
- .map(CredentialOption::getType)
- .collect(Collectors.toList()));
- }
+ List<ProviderSession> providerSessions = prepareProviderSessions(request, session);
if (providerSessions.isEmpty()) {
try {
// TODO: fix
- callback.onResponse(new PrepareGetCredentialResponseInternal(
- false, null, false, false, null));
+ prepareGetCredentialCallback.onResponse(
+ new PrepareGetCredentialResponseInternal(
+ false, null,
+ false, false, null));
} catch (RemoteException e) {
Log.i(
TAG,
@@ -566,14 +526,13 @@ public final class CredentialManagerService
}
}
- finalizeAndEmitInitialPhaseMetric(session);
- // TODO(b/271135048) - May still be worth emitting in the empty cases above.
- providerSessions.forEach(ProviderSession::invokeSession);
+ invokeProviderSessions(providerSessions);
+
+ return cancelTransport;
}
- private void processGetCredential(
+ private List<ProviderSession> prepareProviderSessions(
GetCredentialRequest request,
- IGetCredentialCallback callback,
GetRequestSession session) {
List<ProviderSession> providerSessions;
@@ -623,21 +582,12 @@ public final class CredentialManagerService
.collect(Collectors.toList()));
}
- if (providerSessions.isEmpty()) {
- try {
- callback.onError(
- GetCredentialException.TYPE_NO_CREDENTIAL,
- "No credentials available on this device.");
- } catch (RemoteException e) {
- Slog.e(
- TAG,
- "Issue invoking onError on IGetCredentialCallback "
- + "callback: ", e);
- }
- }
-
finalizeAndEmitInitialPhaseMetric(session);
// TODO(b/271135048) - May still be worth emitting in the empty cases above.
+ return providerSessions;
+ }
+
+ private void invokeProviderSessions(List<ProviderSession> providerSessions) {
providerSessions.forEach(ProviderSession::invokeSession);
}
@@ -936,6 +886,14 @@ public final class CredentialManagerService
}
}
+ private void validateGetCredentialRequest(GetCredentialRequest request) {
+ if (request.getOrigin() != null) {
+ // Check privileged permissions
+ mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
+ }
+ enforcePermissionForAllowedProviders(request);
+ }
+
private void enforcePermissionForAllowedProviders(GetCredentialRequest request) {
boolean containsAllowedProviders = request.getCredentialOptions()
.stream()
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 93f543e62eaf..c0c7be9d80e2 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -151,8 +151,9 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest,
@Override
public void onProviderStatusChanged(ProviderSession.Status status,
- ComponentName componentName) {
- Log.i(TAG, "in onStatusChanged with status: " + status);
+ ComponentName componentName, ProviderSession.CredentialsSource source) {
+ Log.i(TAG, "in onStatusChanged with status: " + status + "and source: " + source);
+
// Auth entry was selected, and it did not have any underlying credentials
if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) {
handleEmptyAuthenticationSelection(componentName);
@@ -173,7 +174,7 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest,
}
}
- private void handleEmptyAuthenticationSelection(ComponentName componentName) {
+ protected void handleEmptyAuthenticationSelection(ComponentName componentName) {
// Update auth entry statuses across different provider sessions
mProviders.keySet().forEach(key -> {
ProviderGetSession session = (ProviderGetSession) mProviders.get(key);
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index 5c93f6b8fd01..c4e480a8e609 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,10 +22,7 @@ import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.credentials.CredentialOption;
-import android.credentials.CredentialProviderInfo;
-import android.credentials.GetCredentialException;
import android.credentials.GetCredentialRequest;
-import android.credentials.GetCredentialResponse;
import android.credentials.IGetCredentialCallback;
import android.credentials.IPrepareGetCredentialCallback;
import android.credentials.PrepareGetCredentialResponseInternal;
@@ -37,32 +34,29 @@ import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.PermissionUtils;
import android.util.Log;
-
-import com.android.server.credentials.metrics.ProviderStatusForMetrics;
+import android.util.Slog;
import java.util.ArrayList;
import java.util.Set;
import java.util.stream.Collectors;
/**
- * Central session for a single prepareGetCredentials request. This class listens to the
- * responses from providers, and the UX app, and updates the provider(S) state.
+ * Central session for a single pendingGetCredential request. This class listens to the
+ * responses from providers, and the UX app, and updates the provider(s) state.
*/
-public class PrepareGetRequestSession extends RequestSession<GetCredentialRequest,
- IGetCredentialCallback, GetCredentialResponse>
- implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
- private static final String TAG = "GetRequestSession";
+public class PrepareGetRequestSession extends GetRequestSession {
+ private static final String TAG = "PrepareGetRequestSession";
private final IPrepareGetCredentialCallback mPrepareGetCredentialCallback;
- private boolean mIsInitialQuery = true;
public PrepareGetRequestSession(Context context, int userId, int callingUid,
- IPrepareGetCredentialCallback prepareGetCredentialCallback,
- IGetCredentialCallback getCredCallback, GetCredentialRequest request,
- CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal,
- long startedTimestamp) {
- super(context, userId, callingUid, request, getCredCallback, RequestInfo.TYPE_GET,
- callingAppInfo, cancellationSignal, startedTimestamp);
+ IGetCredentialCallback callback,
+ GetCredentialRequest request,
+ CallingAppInfo callingAppInfo,
+ CancellationSignal cancellationSignal, long startedTimestamp,
+ IPrepareGetCredentialCallback prepareGetCredentialCallback) {
+ super(context, userId, callingUid, callback, request, callingAppInfo, cancellationSignal,
+ startedTimestamp);
int numTypes = (request.getCredentialOptions().stream()
.map(CredentialOption::getType).collect(
Collectors.toSet())).size(); // Dedupe type strings
@@ -70,146 +64,53 @@ public class PrepareGetRequestSession extends RequestSession<GetCredentialReques
mPrepareGetCredentialCallback = prepareGetCredentialCallback;
}
- /**
- * Creates a new provider session, and adds it list of providers that are contributing to
- * this session.
- *
- * @return the provider session created within this request session, for the given provider
- * info.
- */
- @Override
- @Nullable
- public ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
- RemoteCredentialService remoteCredentialService) {
- ProviderGetSession providerGetSession = ProviderGetSession
- .createNewSession(mContext, mUserId, providerInfo,
- this, remoteCredentialService);
- if (providerGetSession != null) {
- Log.i(TAG, "In startProviderSession - provider session created and being added");
- mProviders.put(providerGetSession.getComponentName().flattenToString(),
- providerGetSession);
- }
- return providerGetSession;
- }
-
- @Override
- protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
- mRequestSessionMetric.collectUiCallStartTime(System.nanoTime());
- try {
- mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
- RequestInfo.newGetRequestInfo(
- mRequestId, mClientRequest, mClientAppInfo.getPackageName()),
- providerDataList));
- } catch (RemoteException e) {
- mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
- respondToClientWithErrorAndFinish(
- GetCredentialException.TYPE_UNKNOWN, "Unable to instantiate selector");
- }
- }
-
- @Override
- protected void invokeClientCallbackSuccess(GetCredentialResponse response)
- throws RemoteException {
- mClientCallback.onResponse(response);
- }
-
- @Override
- protected void invokeClientCallbackError(String errorType, String errorMsg)
- throws RemoteException {
- mClientCallback.onError(errorType, errorMsg);
- }
-
- @Override
- public void onFinalResponseReceived(ComponentName componentName,
- @Nullable GetCredentialResponse response) {
- Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
- mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime());
- mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(mProviders.get(
- componentName.flattenToString()).mProviderSessionMetric
- .getCandidatePhasePerProviderMetric());
- if (response != null) {
- mRequestSessionMetric.collectChosenProviderStatus(
- ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
- respondToClientWithResponseAndFinish(response);
- } else {
- mRequestSessionMetric.collectChosenProviderStatus(
- ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
- respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
- "Invalid response from provider");
- }
- }
-
- //TODO: Try moving the three error & response methods below to RequestSession to be shared
- // between get & create.
@Override
- public void onFinalErrorReceived(ComponentName componentName, String errorType,
- String message) {
- respondToClientWithErrorAndFinish(errorType, message);
- }
-
- @Override
- public void onUiCancellation(boolean isUserCancellation) {
- if (isUserCancellation) {
- respondToClientWithErrorAndFinish(GetCredentialException.TYPE_USER_CANCELED,
- "User cancelled the selector");
- } else {
- respondToClientWithErrorAndFinish(GetCredentialException.TYPE_INTERRUPTED,
- "The UI was interrupted - please try again.");
- }
- }
-
- @Override
- public void onUiSelectorInvocationFailure() {
- respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
- "No credentials available.");
- }
-
- @Override
- public void onProviderStatusChanged(ProviderSession.Status status,
- ComponentName componentName) {
- Log.i(TAG, "in onStatusChanged with status: " + status);
- // Auth entry was selected, and it did not have any underlying credentials
- if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) {
- handleEmptyAuthenticationSelection(componentName);
- return;
- }
- // For any other status, we check if all providers are done and then invoke UI if needed
- if (!isAnyProviderPending()) {
- // If all provider responses have been received, we can either need the UI,
- // or we need to respond with error. The only other case is the entry being
- // selected after the UI has been invoked which has a separate code path.
- if (mIsInitialQuery) {
- // First time in this state. UI shouldn't be invoked because developer wants to
- // punt it for later
+ public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName,
+ ProviderSession.CredentialsSource source) {
+ switch (source) {
+ case REMOTE_PROVIDER:
+ // Remote provider's status changed. We should check if all providers are done, and
+ // if UI invocation is needed.
+ if (isAnyProviderPending()) {
+ // Waiting for a remote provider response
+ return;
+ }
boolean hasQueryCandidatePermission = PermissionUtils.hasPermission(
mContext,
mClientAppInfo.getPackageName(),
Manifest.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS);
if (isUiInvocationNeeded()) {
+ // To avoid extra computation, we only prepare the data at this point when we
+ // know that UI invocation is needed
ArrayList<ProviderData> providerData = getProviderDataForUi();
if (!providerData.isEmpty()) {
constructPendingResponseAndInvokeCallback(hasQueryCandidatePermission,
getCredentialResultTypes(hasQueryCandidatePermission),
- hasAuthenticationResults(providerData, hasQueryCandidatePermission),
+ hasAuthenticationResults(providerData,
+ hasQueryCandidatePermission),
hasRemoteResults(providerData, hasQueryCandidatePermission),
getUiIntent());
- } else {
- constructEmptyPendingResponseAndInvokeCallback(hasQueryCandidatePermission);
+ return;
}
- } else {
- constructEmptyPendingResponseAndInvokeCallback(hasQueryCandidatePermission);
}
- mIsInitialQuery = false;
- } else {
- // Not the first time. This could be a result of a user selection leading to a UI
- // invocation again.
- if (isUiInvocationNeeded()) {
+ // We reach here if Ui invocation is not needed, or provider data is empty
+ constructEmptyPendingResponseAndInvokeCallback(
+ hasQueryCandidatePermission);
+ break;
+
+ case AUTH_ENTRY:
+ // Status updated through a selected authentication entry. We don't need to
+ // check on any other credential source and can process this result directly.
+ if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) {
+ // Update entry subtitle and re-invoke UI
+ super.handleEmptyAuthenticationSelection(componentName);
+ } else if (status == ProviderSession.Status.CREDENTIALS_RECEIVED) {
getProviderDataAndInitiateUi();
- } else {
- respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
- "No credentials available");
}
- }
+ break;
+ default:
+ Slog.w(TAG, "Unexpected source");
+ break;
}
}
@@ -294,34 +195,4 @@ public class PrepareGetRequestSession extends RequestSession<GetCredentialReques
return null;
}
}
-
- private void handleEmptyAuthenticationSelection(ComponentName componentName) {
- // Update auth entry statuses across different provider sessions
- mProviders.keySet().forEach(key -> {
- ProviderGetSession session = (ProviderGetSession) mProviders.get(key);
- if (!session.mComponentName.equals(componentName)) {
- session.updateAuthEntriesStatusFromAnotherSession();
- }
- });
-
- // Invoke UI since it needs to show a snackbar if last auth entry, or a status on each
- // auth entries along with other valid entries
- getProviderDataAndInitiateUi();
-
- // Respond to client if all auth entries are empty and nothing else to show on the UI
- if (providerDataContainsEmptyAuthEntriesOnly()) {
- respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
- "No credentials available");
- }
- }
-
- private boolean providerDataContainsEmptyAuthEntriesOnly() {
- for (String key : mProviders.keySet()) {
- ProviderGetSession session = (ProviderGetSession) mProviders.get(key);
- if (!session.containsEmptyAuthEntriesOnly()) {
- return false;
- }
- }
- return true;
- }
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index 2e7aaa0023b5..1b736e01c842 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -82,7 +82,8 @@ public final class ProviderClearSession extends ProviderSession<ClearCredentialS
public void onProviderResponseSuccess(@Nullable Void response) {
Log.i(TAG, "in onProviderResponseSuccess");
mProviderResponseSet = true;
- updateStatusAndInvokeCallback(Status.COMPLETE);
+ updateStatusAndInvokeCallback(Status.COMPLETE,
+ /*source=*/ CredentialsSource.REMOTE_PROVIDER);
}
/** Called when the provider response resulted in a failure. */
@@ -92,14 +93,16 @@ public final class ProviderClearSession extends ProviderSession<ClearCredentialS
mProviderException = (ClearCredentialStateException) exception;
}
mProviderSessionMetric.collectCandidateExceptionStatus(/*hasException=*/true);
- updateStatusAndInvokeCallback(toStatus(errorCode));
+ updateStatusAndInvokeCallback(toStatus(errorCode),
+ /*source=*/ CredentialsSource.REMOTE_PROVIDER);
}
/** Called when provider service dies. */
@Override // Callback from the remote provider
public void onProviderServiceDied(RemoteCredentialService service) {
if (service.getComponentName().equals(mComponentName)) {
- updateStatusAndInvokeCallback(Status.SERVICE_DEAD);
+ updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
+ /*source=*/ CredentialsSource.REMOTE_PROVIDER);
} else {
Slog.i(TAG, "Component names different in onProviderServiceDied - "
+ "this should not happen");
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index e05eb6091233..bef045f8f890 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -157,14 +157,16 @@ public final class ProviderCreateSession extends ProviderSession<
mProviderException = (CreateCredentialException) exception;
}
mProviderSessionMetric.collectCandidateExceptionStatus(/*hasException=*/true);
- updateStatusAndInvokeCallback(toStatus(errorCode));
+ updateStatusAndInvokeCallback(toStatus(errorCode),
+ /*source=*/ CredentialsSource.REMOTE_PROVIDER);
}
/** Called when provider service dies. */
@Override
public void onProviderServiceDied(RemoteCredentialService service) {
if (service.getComponentName().equals(mComponentName)) {
- updateStatusAndInvokeCallback(Status.SERVICE_DEAD);
+ updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
+ /*source=*/ CredentialsSource.REMOTE_PROVIDER);
} else {
Slog.i(TAG, "Component names different in onProviderServiceDied - "
+ "this should not happen");
@@ -178,10 +180,12 @@ public final class ProviderCreateSession extends ProviderSession<
response.getRemoteCreateEntry());
if (mProviderResponseDataHandler.isEmptyResponse(response)) {
mProviderSessionMetric.collectCandidateEntryMetrics(response);
- updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
+ updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE,
+ /*source=*/ CredentialsSource.REMOTE_PROVIDER);
} else {
mProviderSessionMetric.collectCandidateEntryMetrics(response);
- updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED);
+ updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED,
+ /*source=*/ CredentialsSource.REMOTE_PROVIDER);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index b5f9e539d2f6..427a8945c573 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -118,41 +118,6 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
return null;
}
- /** Creates a new provider session to be used by the request session. */
- @Nullable
- public static ProviderGetSession createNewSession(
- Context context,
- @UserIdInt int userId,
- CredentialProviderInfo providerInfo,
- PrepareGetRequestSession getRequestSession,
- RemoteCredentialService remoteCredentialService) {
- android.credentials.GetCredentialRequest filteredRequest =
- filterOptions(providerInfo.getCapabilities(),
- getRequestSession.mClientRequest,
- providerInfo);
- if (filteredRequest != null) {
- Map<String, CredentialOption> beginGetOptionToCredentialOptionMap =
- new HashMap<>();
- return new ProviderGetSession(
- context,
- providerInfo,
- getRequestSession,
- userId,
- remoteCredentialService,
- constructQueryPhaseRequest(
- filteredRequest, getRequestSession.mClientAppInfo,
- getRequestSession.mClientRequest.alwaysSendAppInfoToProvider(),
- beginGetOptionToCredentialOptionMap),
- filteredRequest,
- getRequestSession.mClientAppInfo,
- beginGetOptionToCredentialOptionMap,
- getRequestSession.mHybridService
- );
- }
- Log.i(TAG, "Unable to create provider session");
- return null;
- }
-
private static BeginGetCredentialRequest constructQueryPhaseRequest(
android.credentials.GetCredentialRequest filteredRequest,
CallingAppInfo callingAppInfo,
@@ -254,14 +219,16 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
mProviderException = (GetCredentialException) exception;
}
mProviderSessionMetric.collectCandidateExceptionStatus(/*hasException=*/true);
- updateStatusAndInvokeCallback(toStatus(errorCode));
+ updateStatusAndInvokeCallback(toStatus(errorCode),
+ /*source=*/ CredentialsSource.REMOTE_PROVIDER);
}
/** Called when provider service dies. */
@Override // Callback from the remote provider
public void onProviderServiceDied(RemoteCredentialService service) {
if (service.getComponentName().equals(mComponentName)) {
- updateStatusAndInvokeCallback(Status.SERVICE_DEAD);
+ updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
+ /*source=*/ CredentialsSource.REMOTE_PROVIDER);
} else {
Slog.i(TAG, "Component names different in onProviderServiceDied - "
+ "this should not happen");
@@ -306,13 +273,15 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
Log.i(TAG, "Additional content received - removing authentication entry");
mProviderResponseDataHandler.removeAuthenticationAction(entryKey);
if (!mProviderResponseDataHandler.isEmptyResponse()) {
- updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
+ updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED,
+ /*source=*/ CredentialsSource.AUTH_ENTRY);
}
} else {
Log.i(TAG, "Additional content not received");
mProviderResponseDataHandler
.updateAuthEntryWithNoCredentialsReceived(entryKey);
- updateStatusAndInvokeCallback(Status.NO_CREDENTIALS_FROM_AUTH_ENTRY);
+ updateStatusAndInvokeCallback(Status.NO_CREDENTIALS_FROM_AUTH_ENTRY,
+ /*source=*/ CredentialsSource.AUTH_ENTRY);
}
break;
case REMOTE_ENTRY_KEY:
@@ -500,11 +469,13 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
// Log the data.
if (mProviderResponseDataHandler.isEmptyResponse(response)) {
mProviderSessionMetric.collectCandidateEntryMetrics(response);
- updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
+ updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE,
+ /*source=*/ CredentialsSource.REMOTE_PROVIDER);
return;
}
mProviderSessionMetric.collectCandidateEntryMetrics(response);
- updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
+ updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED,
+ /*source=*/ CredentialsSource.REMOTE_PROVIDER);
}
/**
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index 8b14757bafed..9cf27210dde0 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -264,7 +264,8 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption
Stream<CredentialEntry>>) filterResult
-> filterResult.mCredentialEntries.stream())
.collect(Collectors.toList());
- updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
+ updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED,
+ /*source=*/ CredentialsSource.REGISTRY);
// TODO(use metric later)
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 090c076467ad..8c0e1c1511e6 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -75,6 +75,12 @@ public abstract class ProviderSession<T, R>
@NonNull
private int mProviderSessionUid;
+ enum CredentialsSource {
+ REMOTE_PROVIDER,
+ REGISTRY,
+ AUTH_ENTRY
+ }
+
/**
* Returns true if the given status reflects that the provider state is ready to be shown
* on the credMan UI.
@@ -118,7 +124,8 @@ public abstract class ProviderSession<T, R>
*/
public interface ProviderInternalCallback<V> {
/** Called when status changes. */
- void onProviderStatusChanged(Status status, ComponentName componentName);
+ void onProviderStatusChanged(Status status, ComponentName componentName,
+ CredentialsSource source);
/** Called when the final credential is received through an entry selection. */
void onFinalResponseReceived(ComponentName componentName, V response);
@@ -206,11 +213,12 @@ public abstract class ProviderSession<T, R>
}
/** Updates the status . */
- protected void updateStatusAndInvokeCallback(@NonNull Status status) {
+ protected void updateStatusAndInvokeCallback(@NonNull Status status,
+ CredentialsSource source) {
setStatus(status);
mProviderSessionMetric.collectCandidateMetricUpdate(isTerminatingStatus(status),
isCompletionStatus(status), mProviderSessionUid);
- mCallbacks.onProviderStatusChanged(status, mComponentName);
+ mCallbacks.onProviderStatusChanged(status, mComponentName, source);
}
/** Common method that transfers metrics from the init phase to candidates */
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index df6f9998c326..c040b1928ce4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -397,6 +397,14 @@ public class JobStatusTest {
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
+
+ // Less than 2 failures, but job is downgraded.
+ numFailures = 1;
+ numSystemStops = 0;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0, 0);
+ job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
+ assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
}
@Test
@@ -500,6 +508,80 @@ public class JobStatusTest {
}
@Test
+ public void testGetEffectivePriority_UserInitiated() {
+ final JobInfo jobInfo =
+ new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setUserInitiated(true)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .build();
+ JobStatus job = createJobStatus(jobInfo);
+
+ // Less than 2 failures, priority shouldn't be affected.
+ assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
+ int numFailures = 1;
+ int numSystemStops = 0;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0, 0);
+ assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
+
+ // 2+ failures, priority shouldn't be affected while job is still a UI job
+ numFailures = 2;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0, 0);
+ assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
+ numFailures = 5;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0, 0);
+ assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
+ numFailures = 8;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0, 0);
+ assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
+
+ // System stops shouldn't factor in the downgrade.
+ numSystemStops = 10;
+ numFailures = 0;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0, 0);
+ assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
+
+ // Job can no long run as user-initiated. Downgrades should be effective.
+ // Priority can't be max.
+ job = createJobStatus(jobInfo);
+ job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
+ assertFalse(job.shouldTreatAsUserInitiatedJob());
+
+ // Less than 2 failures.
+ assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
+ numFailures = 1;
+ numSystemStops = 0;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0, 0);
+ assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
+
+ // 2+ failures, priority should start getting lower
+ numFailures = 2;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0, 0);
+ assertEquals(JobInfo.PRIORITY_DEFAULT, job.getEffectivePriority());
+ numFailures = 5;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0, 0);
+ assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
+ numFailures = 8;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0, 0);
+ assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
+
+ // System stops shouldn't factor in the downgrade.
+ numSystemStops = 10;
+ numFailures = 0;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0, 0);
+ assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
+ }
+
+ @Test
public void testShouldTreatAsUserInitiated() {
JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
.setUserInitiated(false)
diff --git a/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java b/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
index 13d49aa687a8..6bc0fbf3101c 100644
--- a/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
@@ -148,7 +148,7 @@ public class ProviderRegistryGetSessionTest {
assertThat(packageNameCaptor.getValue()).isEqualTo(CALLING_PACKAGE_NAME);
assertThat(flattenedRequestCaptor.getValue()).containsExactly(FLATTENED_REQUEST);
verify(mGetRequestSession).onProviderStatusChanged(statusCaptor.capture(),
- cpComponentNameCaptor.capture());
+ cpComponentNameCaptor.capture(), ProviderSession.CredentialsSource.REGISTRY);
assertThat(statusCaptor.getValue()).isEqualTo(ProviderSession.Status.CREDENTIALS_RECEIVED);
assertThat(cpComponentNameCaptor.getValue()).isEqualTo(CREDENTIAL_PROVIDER_COMPONENT);
assertThat(mProviderRegistryGetSession.mCredentialEntries).hasSize(2);
@@ -245,8 +245,6 @@ public class ProviderRegistryGetSessionTest {
ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY,
entryKey, providerPendingIntentResponse);
- verify(mGetRequestSession).onProviderStatusChanged(statusCaptor.capture(),
- cpComponentNameCaptor.capture());
assertThat(statusCaptor.getValue()).isEqualTo(ProviderSession.Status.CREDENTIALS_RECEIVED);
verify(mGetRequestSession).onFinalErrorReceived(cpComponentNameCaptor.capture(),
exceptionTypeCaptor.capture(), exceptionMessageCaptor.capture());
@@ -279,8 +277,6 @@ public class ProviderRegistryGetSessionTest {
ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY,
entryKey, providerPendingIntentResponse);
- verify(mGetRequestSession).onProviderStatusChanged(statusCaptor.capture(),
- cpComponentNameCaptor.capture());
assertThat(statusCaptor.getValue()).isEqualTo(ProviderSession.Status.CREDENTIALS_RECEIVED);
verify(mGetRequestSession).onFinalErrorReceived(cpComponentNameCaptor.capture(),
exceptionTypeCaptor.capture(), exceptionMessageCaptor.capture());
@@ -313,8 +309,6 @@ public class ProviderRegistryGetSessionTest {
ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY,
entryKey, providerPendingIntentResponse);
- verify(mGetRequestSession).onProviderStatusChanged(statusCaptor.capture(),
- cpComponentNameCaptor.capture());
assertThat(statusCaptor.getValue()).isEqualTo(ProviderSession.Status.CREDENTIALS_RECEIVED);
verify(mGetRequestSession).onFinalResponseReceived(cpComponentNameCaptor.capture(),
getCredentialResponseCaptor.capture());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 226ecf421928..146ed34204f5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -792,7 +792,7 @@ public class DisplayRotationTests {
// ... until half-fold
mTarget.foldStateChanged(DeviceStateController.DeviceState.HALF_FOLDED);
assertTrue(waitForUiHandler());
- verify(sMockWm).updateRotation(anyBoolean(), anyBoolean());
+ verify(sMockWm).updateRotation(false, false);
assertTrue(waitForUiHandler());
assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation(
SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
@@ -800,7 +800,7 @@ public class DisplayRotationTests {
// ... then transition back to flat
mTarget.foldStateChanged(DeviceStateController.DeviceState.OPEN);
assertTrue(waitForUiHandler());
- verify(sMockWm, atLeast(1)).updateRotation(anyBoolean(), anyBoolean());
+ verify(sMockWm, atLeast(1)).updateRotation(false, false);
assertTrue(waitForUiHandler());
assertEquals(Surface.ROTATION_270, mTarget.rotationForOrientation(
SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index ef20f2b8fe64..b35eceb6dd11 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -42,20 +42,20 @@ import org.junit.runner.RunWith;
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
-public class WindowContainerInsetsSourceProviderTest extends WindowTestsBase {
+public class InsetsSourceProviderTest extends WindowTestsBase {
private InsetsSource mSource = new InsetsSource(
InsetsSource.createId(null, 0, statusBars()), statusBars());
- private WindowContainerInsetsSourceProvider mProvider;
+ private InsetsSourceProvider mProvider;
private InsetsSource mImeSource = new InsetsSource(ID_IME, ime());
- private WindowContainerInsetsSourceProvider mImeProvider;
+ private InsetsSourceProvider mImeProvider;
@Before
public void setUp() throws Exception {
mSource.setVisible(true);
- mProvider = new WindowContainerInsetsSourceProvider(mSource,
+ mProvider = new InsetsSourceProvider(mSource,
mDisplayContent.getInsetsStateController(), mDisplayContent);
- mImeProvider = new WindowContainerInsetsSourceProvider(mImeSource,
+ mImeProvider = new InsetsSourceProvider(mImeSource,
mDisplayContent.getInsetsStateController(), mDisplayContent);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 74fde65c4dcd..ff2944a80976 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -287,7 +287,7 @@ public class InsetsStateControllerTest extends WindowTestsBase {
// IME cannot be the IME target.
ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
- WindowContainerInsetsSourceProvider statusBarProvider =
+ InsetsSourceProvider statusBarProvider =
getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars());
final SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>> imeOverrideProviders =
new SparseArray<>();
@@ -353,7 +353,7 @@ public class InsetsStateControllerTest extends WindowTestsBase {
public void testTransientVisibilityOfFixedRotationState() {
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- final WindowContainerInsetsSourceProvider provider = getController()
+ final InsetsSourceProvider provider = getController()
.getOrCreateSourceProvider(ID_STATUS_BAR, statusBars());
provider.setWindowContainer(statusBar, null, null);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 6261e56a87c5..a1ddd5748002 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -484,7 +484,7 @@ public class WindowContainerTests extends WindowTestsBase {
windowState.mSurfaceAnimator).getAnimationType();
assertTrue(parent.isAnimating(CHILDREN));
- windowState.setControllableInsetProvider(mock(WindowContainerInsetsSourceProvider.class));
+ windowState.setControllableInsetProvider(mock(InsetsSourceProvider.class));
assertFalse(parent.isAnimating(CHILDREN));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 373f994f83a4..d19c996ce939 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -801,8 +801,8 @@ public class WindowOrganizerTests extends WindowTestsBase {
new Rect(0, 0, 1080, 200));
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
- assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders
- .valueAt(0).getSource().getType()).isEqualTo(
+ assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSources
+ .valueAt(0).getType()).isEqualTo(
WindowInsets.Type.systemOverlays());
}
@@ -831,7 +831,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
WindowInsets.Type.systemOverlays());
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct2);
- assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders.size()).isEqualTo(0);
+ assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSources.size()).isEqualTo(0);
}
@Test
diff --git a/services/voiceinteraction/OWNERS b/services/voiceinteraction/OWNERS
index ef1061b28b63..40e8d26931e1 100644
--- a/services/voiceinteraction/OWNERS
+++ b/services/voiceinteraction/OWNERS
@@ -1 +1,2 @@
include /core/java/android/service/voice/OWNERS
+include /media/java/android/media/soundtrigger/OWNERS
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/OWNERS b/services/voiceinteraction/java/com/android/server/soundtrigger/OWNERS
index 01b2cb981bbb..1e41886fe716 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/OWNERS
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/OWNERS
@@ -1,2 +1 @@
-atneya@google.com
-elaurent@google.com
+include /media/java/android/media/soundtrigger/OWNERS
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 48a39e682340..f3cb9baedd4b 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -74,9 +74,8 @@ import com.android.server.voiceinteraction.VoiceInteractionManagerServiceImpl.De
import java.io.PrintWriter;
import java.time.Instant;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -122,8 +121,8 @@ final class HotwordDetectionConnection {
private static final int DETECTION_SERVICE_TYPE_VISUAL_QUERY = 2;
// TODO: This may need to be a Handler(looper)
- private final ScheduledExecutorService mScheduledExecutorService =
- Executors.newSingleThreadScheduledExecutor();
+ private final ScheduledThreadPoolExecutor mScheduledExecutorService =
+ new ScheduledThreadPoolExecutor(1);
@Nullable private final ScheduledFuture<?> mCancellationTaskFuture;
private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied;
@NonNull private final ServiceConnectionFactory mHotwordDetectionServiceConnectionFactory;
@@ -210,6 +209,7 @@ final class HotwordDetectionConnection {
if (mReStartPeriodSeconds <= 0) {
mCancellationTaskFuture = null;
} else {
+ mScheduledExecutorService.setRemoveOnCancelPolicy(true);
// TODO: we need to be smarter here, e.g. schedule it a bit more often,
// but wait until the current session is closed.
mCancellationTaskFuture = mScheduledExecutorService.scheduleAtFixedRate(() -> {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
new file mode 100644
index 000000000000..1ccac13c280f
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.helpers
+
+import android.app.Instrumentation
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.device.traces.parsers.toFlickerComponent
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.helpers.FIND_TIMEOUT
+import android.tools.device.helpers.SYSTEMUI_PACKAGE
+
+class LetterboxAppHelper
+@JvmOverloads
+constructor(
+ instr: Instrumentation,
+ launcherName: String = ActivityOptions.NonResizeablePortraitActivity.LABEL,
+ component: ComponentNameMatcher =
+ ActivityOptions.NonResizeablePortraitActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
+
+ fun clickRestart(wmHelper: WindowManagerStateHelper) {
+ val restartButton = uiDevice.wait(Until.findObject(By.res(
+ SYSTEMUI_PACKAGE, "size_compat_restart_button")), FIND_TIMEOUT)
+ restartButton?.run { restartButton.click() } ?: error("Restart button not found")
+
+ // size compat mode restart confirmation dialog button
+ val restartDialogButton = uiDevice.wait(Until.findObject(By.res(
+ SYSTEMUI_PACKAGE, "letterbox_restart_dialog_restart_button")), FIND_TIMEOUT)
+ restartDialogButton?.run { restartDialogButton.click() }
+ ?: error("Restart dialog button not found")
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 5361d73f93b5..1ec9ec9b0eda 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -88,6 +88,18 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".NonResizeablePortraitActivity"
+ android:theme="@style/CutoutNever"
+ android:resizeableActivity="false"
+ android:screenOrientation="portrait"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeablePortraitActivity"
+ android:label="NonResizeablePortraitActivity"
+ 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=".LaunchNewActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewActivity"
android:theme="@style/CutoutShortEdges"
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 b61bc0ccf17e..9c3226b5292c 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
@@ -67,6 +67,12 @@ public class ActivityOptions {
FLICKER_APP_PACKAGE + ".NonResizeableActivity");
}
+ public static class NonResizeablePortraitActivity {
+ public static final String LABEL = "NonResizeablePortraitActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".NonResizeablePortraitActivity");
+ }
+
public static class DialogThemedActivity {
public static final String LABEL = "DialogThemedActivity";
public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeHeightLog.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeablePortraitActivity.java
index 6fe868655ee6..4b420dcea54d 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeHeightLog.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeablePortraitActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,20 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.log.dagger;
+package com.android.server.wm.flicker.testapp;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import android.app.Activity;
+import android.os.Bundle;
-import com.android.systemui.plugins.log.LogBuffer;
+public class NonResizeablePortraitActivity extends Activity {
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Qualifier;
-
-/** A {@link LogBuffer} for Shade height changes. */
-@Qualifier
-@Documented
-@Retention(RUNTIME)
-public @interface ShadeHeightLog {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.activity_non_resizeable);
+ }
}
diff --git a/tests/SoundTriggerTestApp/OWNERS b/tests/SoundTriggerTestApp/OWNERS
index 9db19a37812b..a0fcfc52704d 100644
--- a/tests/SoundTriggerTestApp/OWNERS
+++ b/tests/SoundTriggerTestApp/OWNERS
@@ -1,2 +1,2 @@
-include /core/java/android/media/soundtrigger/OWNERS
+include /media/java/android/media/soundtrigger/OWNERS
mdooley@google.com
diff --git a/tests/SoundTriggerTests/OWNERS b/tests/SoundTriggerTests/OWNERS
index 816bc6bba639..1e41886fe716 100644
--- a/tests/SoundTriggerTests/OWNERS
+++ b/tests/SoundTriggerTests/OWNERS
@@ -1 +1 @@
-include /core/java/android/media/soundtrigger/OWNERS
+include /media/java/android/media/soundtrigger/OWNERS
diff --git a/tests/utils/testutils/java/android/os/test/FakePermissionEnforcer.java b/tests/utils/testutils/java/android/os/test/FakePermissionEnforcer.java
new file mode 100644
index 000000000000..b94bb41c0988
--- /dev/null
+++ b/tests/utils/testutils/java/android/os/test/FakePermissionEnforcer.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.test;
+
+import static android.permission.PermissionManager.PERMISSION_GRANTED;
+import static android.permission.PermissionManager.PERMISSION_HARD_DENIED;
+
+import android.annotation.NonNull;
+import android.content.AttributionSource;
+import android.os.PermissionEnforcer;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Fake for {@link PermissionEnforcer}. Useful for tests wanting to mock the
+ * permission checks of an AIDL service. FakePermissionEnforcer may be passed
+ * to the constructor of the AIDL-generated Stub class.
+ *
+ */
+public class FakePermissionEnforcer extends PermissionEnforcer {
+ private Set<String> mGranted;
+
+ public FakePermissionEnforcer() {
+ mGranted = new HashSet();
+ }
+
+ public void grant(String permission) {
+ mGranted.add(permission);
+ }
+
+ public void revoke(String permission) {
+ mGranted.remove(permission);
+ }
+
+ private boolean granted(String permission) {
+ return mGranted.contains(permission);
+ }
+
+ @Override
+ protected int checkPermission(@NonNull String permission,
+ @NonNull AttributionSource source) {
+ return granted(permission) ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED;
+ }
+
+ @Override
+ protected int checkPermission(@NonNull String permission, int pid, int uid) {
+ return granted(permission) ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED;
+ }
+}
diff --git a/tests/utils/testutils/java/android/os/test/OWNERS b/tests/utils/testutils/java/android/os/test/OWNERS
new file mode 100644
index 000000000000..3a9129e1bb69
--- /dev/null
+++ b/tests/utils/testutils/java/android/os/test/OWNERS
@@ -0,0 +1 @@
+per-file FakePermissionEnforcer.java = file:/tests/EnforcePermission/OWNERS
diff --git a/tests/vcn/java/android/net/vcn/VcnConfigTest.java b/tests/vcn/java/android/net/vcn/VcnConfigTest.java
index b313c9fc6c28..73a0a6183cb6 100644
--- a/tests/vcn/java/android/net/vcn/VcnConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnConfigTest.java
@@ -17,6 +17,7 @@
package android.net.vcn;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static org.junit.Assert.assertEquals;
@@ -160,6 +161,37 @@ public class VcnConfigTest {
assertNotEquals(config, configNotEqual);
}
+ private VcnConfig buildConfigRestrictTransportTest(boolean isTestMode) throws Exception {
+ VcnConfig.Builder builder =
+ new VcnConfig.Builder(mContext)
+ .setRestrictedUnderlyingNetworkTransports(Set.of(TRANSPORT_TEST));
+ if (isTestMode) {
+ builder.setIsTestModeProfile();
+ }
+
+ for (VcnGatewayConnectionConfig gatewayConnectionConfig : GATEWAY_CONNECTION_CONFIGS) {
+ builder.addGatewayConnectionConfig(gatewayConnectionConfig);
+ }
+
+ return builder.build();
+ }
+
+ @Test
+ public void testRestrictTransportTestInTestModeProfile() throws Exception {
+ final VcnConfig config = buildConfigRestrictTransportTest(true /* isTestMode */);
+ assertEquals(Set.of(TRANSPORT_TEST), config.getRestrictedUnderlyingNetworkTransports());
+ }
+
+ @Test
+ public void testRestrictTransportTestInNonTestModeProfile() throws Exception {
+ try {
+ buildConfigRestrictTransportTest(false /* isTestMode */);
+ fail("Expected exception because the config is not a test mode profile");
+ } catch (Exception expected) {
+
+ }
+ }
+
@Test
public void testParceling() {
final VcnConfig config = buildTestConfig(mContext);
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
index 629e988495cc..226604108522 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
@@ -95,6 +95,7 @@ public class NetworkPriorityClassifierTest {
private static final NetworkCapabilities WIFI_NETWORK_CAPABILITIES =
new NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.setSignalStrength(WIFI_RSSI)
.setSsid(SSID)
.setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS)
@@ -509,12 +510,14 @@ public class NetworkPriorityClassifierTest {
VcnCellUnderlyingNetworkTemplate template, boolean expectMatch) {
assertEquals(
expectMatch,
- checkMatchesCellPriorityRule(
+ checkMatchesPriorityRule(
mVcnContext,
template,
mCellNetworkRecord,
SUB_GROUP,
- mSubscriptionSnapshot));
+ mSubscriptionSnapshot,
+ null /* currentlySelected */,
+ null /* carrierConfig */));
}
@Test