summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp2
-rw-r--r--StubLibraries.bp2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/Agent.java57
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java7
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/Ledger.java5
-rw-r--r--cmds/bootanimation/BootAnimation.cpp3
-rw-r--r--core/api/current.txt15
-rw-r--r--core/api/module-lib-current.txt4
-rw-r--r--core/api/system-current.txt132
-rw-r--r--core/api/test-current.txt8
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java29
-rw-r--r--core/java/android/animation/ValueAnimator.java7
-rw-r--r--core/java/android/app/ActivityThread.java16
-rw-r--r--core/java/android/app/ContextImpl.java4
-rw-r--r--core/java/android/app/IActivityManager.aidl4
-rw-r--r--core/java/android/app/Notification.java1
-rw-r--r--core/java/android/app/SystemServiceRegistry.java13
-rw-r--r--core/java/android/companion/CompanionDeviceManager.java45
-rw-r--r--core/java/android/companion/CompanionDeviceService.java86
-rw-r--r--core/java/android/companion/ICompanionDeviceManager.aidl5
-rw-r--r--core/java/android/companion/ICompanionDeviceService.aidl6
-rw-r--r--core/java/android/companion/virtual/IVirtualDevice.aidl32
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceManager.java91
-rw-r--r--core/java/android/content/res/Resources.java11
-rw-r--r--core/java/android/content/res/TypedArray.java47
-rw-r--r--core/java/android/hardware/HardwareBuffer.java10
-rw-r--r--core/java/android/hardware/input/VirtualKeyEvent.aidl19
-rw-r--r--core/java/android/hardware/input/VirtualKeyEvent.java152
-rw-r--r--core/java/android/hardware/input/VirtualKeyboard.java72
-rw-r--r--core/java/android/hardware/input/VirtualMouse.java102
-rw-r--r--core/java/android/hardware/input/VirtualMouseButtonEvent.aidl19
-rw-r--r--core/java/android/hardware/input/VirtualMouseButtonEvent.java180
-rw-r--r--core/java/android/hardware/input/VirtualMouseRelativeEvent.aidl19
-rw-r--r--core/java/android/hardware/input/VirtualMouseRelativeEvent.java119
-rw-r--r--core/java/android/hardware/input/VirtualMouseScrollEvent.aidl19
-rw-r--r--core/java/android/hardware/input/VirtualMouseScrollEvent.java129
-rw-r--r--core/java/android/hardware/input/VirtualTouchEvent.aidl19
-rw-r--r--core/java/android/hardware/input/VirtualTouchEvent.java299
-rw-r--r--core/java/android/hardware/input/VirtualTouchscreen.java71
-rw-r--r--core/java/android/os/Binder.java103
-rw-r--r--core/java/android/os/BinderProxy.java2
-rw-r--r--core/java/android/os/GraphicsEnvironment.java7
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java5
-rw-r--r--core/java/android/text/StaticLayout.java12
-rw-r--r--core/java/android/util/IntArray.java8
-rw-r--r--core/java/android/view/MotionEvent.java9
-rw-r--r--core/java/android/view/SurfaceControl.java135
-rw-r--r--core/java/android/view/inputmethod/InputConnection.java31
-rw-r--r--core/java/android/view/translation/UiTranslationController.java5
-rw-r--r--core/java/android/widget/TextViewTranslationCallback.java11
-rw-r--r--core/java/android/window/WindowTokenClient.java10
-rw-r--r--core/java/com/android/internal/inputmethod/EditableInputConnection.java2
-rw-r--r--core/jni/android_view_SurfaceControl.cpp62
-rw-r--r--core/res/AndroidManifest.xml2
-rw-r--r--core/res/res/values/config.xml2
-rw-r--r--core/tests/coretests/Android.bp1
-rw-r--r--core/tests/coretests/src/android/hardware/input/VirtualKeyEventTest.java60
-rw-r--r--core/tests/coretests/src/android/hardware/input/VirtualMouseButtonEventTest.java59
-rw-r--r--core/tests/coretests/src/android/hardware/input/VirtualMouseRelativeEventTest.java37
-rw-r--r--core/tests/coretests/src/android/hardware/input/VirtualMouseScrollEventTest.java54
-rw-r--r--core/tests/coretests/src/android/hardware/input/VirtualTouchEventTest.java172
-rw-r--r--core/tests/coretests/src/android/os/AidlTest.java22
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java5
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java24
-rw-r--r--libs/WindowManager/Shell/res/drawable/compat_hint_bubble.xml (renamed from libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml)4
-rw-r--r--libs/WindowManager/Shell/res/drawable/compat_hint_point.xml (renamed from libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml)4
-rw-r--r--libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml6
-rw-r--r--libs/WindowManager/Shell/res/layout/compat_mode_hint.xml46
-rw-r--r--libs/WindowManager/Shell/res/layout/compat_ui_layout.xml (renamed from libs/WindowManager/Shell/res/layout/size_compat_ui.xml)11
-rw-r--r--libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml58
-rw-r--r--libs/WindowManager/Shell/res/values/colors.xml6
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java56
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java72
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java)8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java)79
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java89
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java321
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java66
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java76
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java344
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java127
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java32
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java)48
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java)58
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java253
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java85
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java284
-rwxr-xr-xmedia/java/android/media/IAudioService.aidl4
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicy.java42
-rw-r--r--media/java/android/media/tv/tuner/Tuner.java61
-rw-r--r--media/jni/android_media_tv_Tuner.cpp119
-rw-r--r--media/jni/android_media_tv_Tuner.h16
-rw-r--r--native/android/surface_control.cpp2
-rw-r--r--packages/CarrierDefaultApp/AndroidManifest.xml1
-rw-r--r--packages/ConnectivityT/framework-t/Android.bp14
-rw-r--r--packages/ConnectivityT/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java52
-rw-r--r--packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java90
-rw-r--r--packages/SystemUI/AndroidManifest.xml1
-rw-r--r--packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml5
-rw-r--r--packages/SystemUI/res-keyguard/drawable/unlocked_to_aod_lock.xml169
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml13
-rw-r--r--packages/SystemUI/res-keyguard/values/dimens.xml8
-rw-r--r--packages/SystemUI/res/drawable/ic_signal_wifi_off.xml24
-rw-r--r--packages/SystemUI/res/layout/dream_overlay_container.xml61
-rw-r--r--packages/SystemUI/res/values-h800dp/dimens.xml2
-rw-r--r--packages/SystemUI/res/values/dimens.xml13
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java315
-rw-r--r--packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt370
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java20
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java23
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java13
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java13
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIFactory.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeUi.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerView.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java76
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java86
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java172
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java105
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java67
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/PendingDrawnTasksContainer.kt82
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt173
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/condition/dagger/MonitorComponent.java (renamed from packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalConditionsMonitor.java)34
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java18
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java32
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java45
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java195
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java86
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt216
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java34
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java10
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlay/res/values-land/dimens.xml2
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml8
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml8
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml8
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml8
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java6
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java121
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java65
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java89
-rw-r--r--services/companion/java/com/android/server/companion/virtual/InputController.java283
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java278
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java80
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java5
-rw-r--r--services/core/java/com/android/server/app/GameManagerService.java7
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java21
-rw-r--r--services/core/java/com/android/server/audio/MediaFocusControl.java46
-rw-r--r--services/core/java/com/android/server/communal/OWNERS3
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodBindingController.java76
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java77
-rw-r--r--services/core/java/com/android/server/location/provider/LocationProviderManager.java63
-rw-r--r--services/core/java/com/android/server/notification/PermissionHelper.java12
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java15
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java50
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java1
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java4
-rw-r--r--services/core/java/com/android/server/wm/LockTaskController.java48
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java2
-rw-r--r--services/core/jni/Android.bp1
-rw-r--r--services/core/jni/com_android_server_companion_virtual_InputController.cpp455
-rw-r--r--services/core/jni/onload.cpp2
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt21
-rw-r--r--services/tests/servicestests/Android.bp1
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java33
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java313
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java1
-rw-r--r--telephony/java/android/telephony/ims/RcsClientConfiguration.java50
-rw-r--r--telephony/java/android/telephony/ims/RcsUceAdapter.java38
-rw-r--r--telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java20
-rw-r--r--telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl2
-rw-r--r--telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java24
-rw-r--r--telephony/java/com/android/internal/telephony/RILConstants.java2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt2
224 files changed, 8335 insertions, 2463 deletions
diff --git a/Android.bp b/Android.bp
index 54983d615913..3b8ef617630b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -156,7 +156,6 @@ java_library {
"framework-scheduling.stubs.module_lib",
"framework-sdkextensions.stubs.module_lib",
"framework-statsd.stubs.module_lib",
- "framework-supplementalapi.stubs.module_lib",
"framework-supplementalprocess.stubs.module_lib",
"framework-tethering.stubs.module_lib",
"framework-uwb.stubs.module_lib",
@@ -181,7 +180,6 @@ java_library {
"framework-scheduling.impl",
"framework-sdkextensions.impl",
"framework-statsd.impl",
- "framework-supplementalapi.impl",
"framework-supplementalprocess.impl",
"framework-tethering.impl",
"framework-uwb.impl",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 92c63c9ab841..3b11036495c8 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -250,7 +250,6 @@ modules_public_stubs = [
"framework-scheduling.stubs",
"framework-sdkextensions.stubs",
"framework-statsd.stubs",
- "framework-supplementalapi.stubs",
"framework-supplementalprocess.stubs",
"framework-tethering.stubs",
"framework-uwb.stubs",
@@ -273,7 +272,6 @@ modules_system_stubs = [
"framework-scheduling.stubs.system",
"framework-sdkextensions.stubs.system",
"framework-statsd.stubs.system",
- "framework-supplementalapi.stubs",
"framework-supplementalprocess.stubs",
"framework-tethering.stubs.system",
"framework-uwb.stubs.system",
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index 005c447c7220..a6a007f46b58 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -46,6 +46,7 @@ import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArrayMap;
+import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -681,8 +682,8 @@ class Agent {
final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName);
final double perc = batteryLevel / 100d;
// TODO: maybe don't give credits to bankrupt apps until battery level >= 50%
- if (ledger.getCurrentBalance() < minBalance) {
- final long shortfall = minBalance - getBalanceLocked(userId, pkgName);
+ final long shortfall = minBalance - ledger.getCurrentBalance();
+ if (shortfall > 0) {
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(now, now, REGULATION_BASIC_INCOME,
null, (long) (perc * shortfall)), true);
@@ -1170,5 +1171,57 @@ class Agent {
void dumpLocked(IndentingPrintWriter pw) {
pw.println();
mBalanceThresholdAlarmQueue.dump(pw);
+
+ pw.println();
+ pw.println("Ongoing events:");
+ pw.increaseIndent();
+ boolean printedEvents = false;
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ for (int u = mCurrentOngoingEvents.numMaps() - 1; u >= 0; --u) {
+ final int userId = mCurrentOngoingEvents.keyAt(u);
+ for (int p = mCurrentOngoingEvents.numElementsForKey(userId) - 1; p >= 0; --p) {
+ final String pkgName = mCurrentOngoingEvents.keyAt(u, p);
+ final SparseArrayMap<String, OngoingEvent> ongoingEvents =
+ mCurrentOngoingEvents.get(userId, pkgName);
+
+ boolean printedApp = false;
+
+ for (int e = ongoingEvents.numMaps() - 1; e >= 0; --e) {
+ final int eventId = ongoingEvents.keyAt(e);
+ for (int t = ongoingEvents.numElementsForKey(eventId) - 1; t >= 0; --t) {
+ if (!printedApp) {
+ printedApp = true;
+ pw.println(appToString(userId, pkgName));
+ pw.increaseIndent();
+ }
+ printedEvents = true;
+
+ OngoingEvent ongoingEvent = ongoingEvents.valueAt(e, t);
+
+ pw.print(EconomicPolicy.eventToString(ongoingEvent.eventId));
+ if (ongoingEvent.tag != null) {
+ pw.print("(");
+ pw.print(ongoingEvent.tag);
+ pw.print(")");
+ }
+ pw.print(" runtime=");
+ TimeUtils.formatDuration(nowElapsed - ongoingEvent.startTimeElapsed, pw);
+ pw.print(" delta/sec=");
+ pw.print(ongoingEvent.deltaPerSec);
+ pw.print(" refCount=");
+ pw.print(ongoingEvent.refCount);
+ pw.println();
+ }
+ }
+
+ if (printedApp) {
+ pw.decreaseIndent();
+ }
+ }
+ }
+ if (!printedEvents) {
+ pw.print("N/A");
+ }
+ pw.decreaseIndent();
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 20a300abe72a..36895a5c3a25 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -567,20 +567,23 @@ public class InternalResourceService extends SystemService {
final String pkgName = event.getPackageName();
if (DEBUG) {
Slog.d(TAG, "Processing event " + event.getEventType()
+ + " (" + event.mInstanceId + ")"
+ " for " + appToString(userId, pkgName));
}
final long nowElapsed = SystemClock.elapsedRealtime();
switch (event.getEventType()) {
case UsageEvents.Event.ACTIVITY_RESUMED:
mAgent.noteOngoingEventLocked(userId, pkgName,
- EconomicPolicy.REWARD_TOP_ACTIVITY, null, nowElapsed);
+ EconomicPolicy.REWARD_TOP_ACTIVITY, String.valueOf(event.mInstanceId),
+ nowElapsed);
break;
case UsageEvents.Event.ACTIVITY_PAUSED:
case UsageEvents.Event.ACTIVITY_STOPPED:
case UsageEvents.Event.ACTIVITY_DESTROYED:
final long now = getCurrentTimeMillis();
mAgent.stopOngoingActionLocked(userId, pkgName,
- EconomicPolicy.REWARD_TOP_ACTIVITY, null, nowElapsed, now);
+ EconomicPolicy.REWARD_TOP_ACTIVITY, String.valueOf(event.mInstanceId),
+ nowElapsed, now);
break;
case UsageEvents.Event.USER_INTERACTION:
case UsageEvents.Event.CHOOSER_ACTION:
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
index a234ae6142fc..f4917ad82761 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
@@ -138,6 +138,11 @@ class Ledger {
dumpTime(pw, transaction.endTimeMs);
pw.print(": ");
pw.print(EconomicPolicy.eventToString(transaction.eventId));
+ if (transaction.tag != null) {
+ pw.print("(");
+ pw.print(transaction.tag);
+ pw.print(")");
+ }
pw.print(" --> ");
pw.println(narcToString(transaction.delta));
}
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index af4053fb6c8c..05a0661914dc 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -105,6 +105,7 @@ static const int TEXT_MISSING_VALUE = INT_MIN;
static const char EXIT_PROP_NAME[] = "service.bootanim.exit";
static const char PROGRESS_PROP_NAME[] = "service.bootanim.progress";
static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays";
+static const char CLOCK_ENABLED_PROP_NAME[] = "persist.sys.bootanim.clock.enabled";
static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1;
static constexpr size_t TEXT_POS_LEN_MAX = 16;
static const int DYNAMIC_COLOR_COUNT = 4;
@@ -1320,6 +1321,8 @@ bool BootAnimation::movie() {
}
if (!anyPartHasClock) {
mClockEnabled = false;
+ } else if (!android::base::GetBoolProperty(CLOCK_ENABLED_PROP_NAME, false)) {
+ mClockEnabled = false;
}
// Check if npot textures are supported
diff --git a/core/api/current.txt b/core/api/current.txt
index 7f46577505ad..ee44198dcc26 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -10127,8 +10127,10 @@ package android.companion {
ctor public CompanionDeviceService();
method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void dispatchMessage(int, int, @NonNull byte[]);
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
- method @MainThread public abstract void onDeviceAppeared(@NonNull String);
- method @MainThread public abstract void onDeviceDisappeared(@NonNull String);
+ method @Deprecated @MainThread public void onDeviceAppeared(@NonNull String);
+ method @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
+ method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String);
+ method @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
method @MainThread public void onDispatchMessage(int, int, @NonNull byte[]);
field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
}
@@ -17886,6 +17888,7 @@ package android.hardware {
field public static final int RGB_565 = 4; // 0x4
field public static final int RGB_888 = 3; // 0x3
field public static final int S_UI8 = 53; // 0x35
+ field public static final long USAGE_COMPOSER_OVERLAY = 2048L; // 0x800L
field public static final long USAGE_CPU_READ_OFTEN = 3L; // 0x3L
field public static final long USAGE_CPU_READ_RARELY = 2L; // 0x2L
field public static final long USAGE_CPU_WRITE_OFTEN = 48L; // 0x30L
@@ -49044,6 +49047,7 @@ package android.view {
method @NonNull public android.view.SurfaceControl build();
method @NonNull public android.view.SurfaceControl.Builder setBufferSize(@IntRange(from=0) int, @IntRange(from=0) int);
method @NonNull public android.view.SurfaceControl.Builder setFormat(int);
+ method @NonNull public android.view.SurfaceControl.Builder setHidden(boolean);
method @NonNull public android.view.SurfaceControl.Builder setName(@NonNull String);
method @NonNull public android.view.SurfaceControl.Builder setOpaque(boolean);
method @NonNull public android.view.SurfaceControl.Builder setParent(@Nullable android.view.SurfaceControl);
@@ -49058,11 +49062,18 @@ package android.view {
method @NonNull public android.view.SurfaceControl.Transaction merge(@NonNull android.view.SurfaceControl.Transaction);
method @NonNull public android.view.SurfaceControl.Transaction reparent(@NonNull android.view.SurfaceControl, @Nullable android.view.SurfaceControl);
method @NonNull public android.view.SurfaceControl.Transaction setAlpha(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0, to=1.0) float);
+ method @NonNull public android.view.SurfaceControl.Transaction setBuffer(@NonNull android.view.SurfaceControl, @Nullable android.hardware.HardwareBuffer);
method @NonNull public android.view.SurfaceControl.Transaction setBufferSize(@NonNull android.view.SurfaceControl, @IntRange(from=0) int, @IntRange(from=0) int);
+ method @NonNull public android.view.SurfaceControl.Transaction setBufferTransform(@NonNull android.view.SurfaceControl, int);
+ method @NonNull public android.view.SurfaceControl.Transaction setCrop(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect);
+ method @NonNull public android.view.SurfaceControl.Transaction setDamageRegion(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Region);
method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int);
method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int, int);
method @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int);
method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int);
+ method @NonNull public android.view.SurfaceControl.Transaction setOpaque(@NonNull android.view.SurfaceControl, boolean);
+ method @NonNull public android.view.SurfaceControl.Transaction setPosition(@NonNull android.view.SurfaceControl, float, float);
+ method @NonNull public android.view.SurfaceControl.Transaction setScale(@NonNull android.view.SurfaceControl, float, float);
method @NonNull public android.view.SurfaceControl.Transaction setVisibility(@NonNull android.view.SurfaceControl, boolean);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControl.Transaction> CREATOR;
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index b8ce02e72571..77569063d87a 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -215,6 +215,10 @@ package android.media.session {
package android.net {
+ public final class ConnectivityFrameworkInitializerTiramisu {
+ method public static void registerServiceWrappers();
+ }
+
public final class EthernetNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
ctor public EthernetNetworkSpecifier(@NonNull String);
method public int describeContents();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 654be5bed8c8..5a6ea8962d2f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -70,6 +70,7 @@ package android {
field public static final String BIND_TRANSLATION_SERVICE = "android.permission.BIND_TRANSLATION_SERVICE";
field public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
+ field public static final String BLUETOOTH_MAP = "android.permission.BLUETOOTH_MAP";
field public static final String BRICK = "android.permission.BRICK";
field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
@@ -2428,6 +2429,8 @@ package android.companion {
method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean canPairWithoutPrompt(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
method @NonNull @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public java.util.List<android.companion.AssociationInfo> getAllAssociations();
method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociatedForWifiConnection(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public void notifyDeviceAppeared(int);
+ method @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public void notifyDeviceDisappeared(int);
method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void removeOnAssociationsChangedListener(@NonNull android.companion.CompanionDeviceManager.OnAssociationsChangedListener);
}
@@ -3897,6 +3900,127 @@ package android.hardware.hdmi {
}
+package android.hardware.input {
+
+ public final class VirtualKeyEvent implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAction();
+ method public int getKeyCode();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int ACTION_DOWN = 0; // 0x0
+ field public static final int ACTION_UP = 1; // 0x1
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualKeyEvent> CREATOR;
+ }
+
+ public static final class VirtualKeyEvent.Builder {
+ ctor public VirtualKeyEvent.Builder();
+ method @NonNull public android.hardware.input.VirtualKeyEvent build();
+ method @NonNull public android.hardware.input.VirtualKeyEvent.Builder setAction(int);
+ method @NonNull public android.hardware.input.VirtualKeyEvent.Builder setKeyCode(int);
+ }
+
+ public class VirtualKeyboard implements java.io.Closeable {
+ method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
+ method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendKeyEvent(@NonNull android.hardware.input.VirtualKeyEvent);
+ }
+
+ public class VirtualMouse implements java.io.Closeable {
+ method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
+ method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendButtonEvent(@NonNull android.hardware.input.VirtualMouseButtonEvent);
+ method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendRelativeEvent(@NonNull android.hardware.input.VirtualMouseRelativeEvent);
+ method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendScrollEvent(@NonNull android.hardware.input.VirtualMouseScrollEvent);
+ }
+
+ public final class VirtualMouseButtonEvent implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAction();
+ method public int getButtonCode();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int ACTION_BUTTON_PRESS = 11; // 0xb
+ field public static final int ACTION_BUTTON_RELEASE = 12; // 0xc
+ field public static final int BUTTON_BACK = 8; // 0x8
+ field public static final int BUTTON_FORWARD = 16; // 0x10
+ field public static final int BUTTON_PRIMARY = 1; // 0x1
+ field public static final int BUTTON_SECONDARY = 2; // 0x2
+ field public static final int BUTTON_TERTIARY = 4; // 0x4
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualMouseButtonEvent> CREATOR;
+ }
+
+ public static final class VirtualMouseButtonEvent.Builder {
+ ctor public VirtualMouseButtonEvent.Builder();
+ method @NonNull public android.hardware.input.VirtualMouseButtonEvent build();
+ method @NonNull public android.hardware.input.VirtualMouseButtonEvent.Builder setAction(int);
+ method @NonNull public android.hardware.input.VirtualMouseButtonEvent.Builder setButtonCode(int);
+ }
+
+ public final class VirtualMouseRelativeEvent implements android.os.Parcelable {
+ method public int describeContents();
+ method public float getRelativeX();
+ method public float getRelativeY();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualMouseRelativeEvent> CREATOR;
+ }
+
+ public static final class VirtualMouseRelativeEvent.Builder {
+ ctor public VirtualMouseRelativeEvent.Builder();
+ method @NonNull public android.hardware.input.VirtualMouseRelativeEvent build();
+ method @NonNull public android.hardware.input.VirtualMouseRelativeEvent.Builder setRelativeX(float);
+ method @NonNull public android.hardware.input.VirtualMouseRelativeEvent.Builder setRelativeY(float);
+ }
+
+ public final class VirtualMouseScrollEvent implements android.os.Parcelable {
+ method public int describeContents();
+ method public float getXAxisMovement();
+ method public float getYAxisMovement();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualMouseScrollEvent> CREATOR;
+ }
+
+ public static final class VirtualMouseScrollEvent.Builder {
+ ctor public VirtualMouseScrollEvent.Builder();
+ method @NonNull public android.hardware.input.VirtualMouseScrollEvent build();
+ method @NonNull public android.hardware.input.VirtualMouseScrollEvent.Builder setXAxisMovement(@FloatRange(from=-1.0F, to=1.0f) float);
+ method @NonNull public android.hardware.input.VirtualMouseScrollEvent.Builder setYAxisMovement(@FloatRange(from=-1.0F, to=1.0f) float);
+ }
+
+ public final class VirtualTouchEvent implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAction();
+ method public float getMajorAxisSize();
+ method public int getPointerId();
+ method public float getPressure();
+ method public int getToolType();
+ method public float getX();
+ method public float getY();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int ACTION_CANCEL = 3; // 0x3
+ field public static final int ACTION_DOWN = 0; // 0x0
+ field public static final int ACTION_MOVE = 2; // 0x2
+ field public static final int ACTION_UP = 1; // 0x1
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualTouchEvent> CREATOR;
+ field public static final int TOOL_TYPE_FINGER = 1; // 0x1
+ field public static final int TOOL_TYPE_PALM = 5; // 0x5
+ }
+
+ public static final class VirtualTouchEvent.Builder {
+ ctor public VirtualTouchEvent.Builder();
+ method @NonNull public android.hardware.input.VirtualTouchEvent build();
+ method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setAction(int);
+ method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setMajorAxisSize(@FloatRange(from=0.0f) float);
+ method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setPointerId(int);
+ method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setPressure(@FloatRange(from=0.0f) float);
+ method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setToolType(int);
+ method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setX(float);
+ method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setY(float);
+ }
+
+ public class VirtualTouchscreen implements java.io.Closeable {
+ method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
+ method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendTouchEvent(@NonNull android.hardware.input.VirtualTouchEvent);
+ }
+
+}
+
package android.hardware.lights {
public final class LightState implements android.os.Parcelable {
@@ -5752,9 +5876,11 @@ package android.media.audiopolicy {
method public android.media.AudioTrack createAudioTrackSource(android.media.audiopolicy.AudioMix) throws java.lang.IllegalArgumentException;
method public int detachMixes(@NonNull java.util.List<android.media.audiopolicy.AudioMix>);
method public int getFocusDuckingBehavior();
+ method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioFocusInfo> getFocusStack();
method public int getStatus();
method public boolean removeUidDeviceAffinity(int);
method public boolean removeUserIdDeviceAffinity(int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean sendFocusLoss(@NonNull android.media.AudioFocusInfo) throws java.lang.IllegalStateException;
method public int setFocusDuckingBehavior(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setRegistration(String);
method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
@@ -13911,12 +14037,14 @@ package android.telephony.ims {
}
public final class RcsClientConfiguration implements android.os.Parcelable {
- ctor public RcsClientConfiguration(@NonNull String, @NonNull String, @NonNull String, @NonNull String);
+ ctor @Deprecated public RcsClientConfiguration(@NonNull String, @NonNull String, @NonNull String, @NonNull String);
+ ctor public RcsClientConfiguration(@NonNull String, @NonNull String, @NonNull String, @NonNull String, boolean);
method public int describeContents();
method @NonNull public String getClientVendor();
method @NonNull public String getClientVersion();
method @NonNull public String getRcsProfile();
method @NonNull public String getRcsVersion();
+ method public boolean isRcsEnabledByUser();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsClientConfiguration> CREATOR;
field public static final String RCS_PROFILE_1_0 = "UP_1.0";
@@ -14053,6 +14181,7 @@ package android.telephony.ims {
field public static final int PUBLISH_STATE_NOT_PUBLISHED = 2; // 0x2
field public static final int PUBLISH_STATE_OK = 1; // 0x1
field public static final int PUBLISH_STATE_OTHER_ERROR = 6; // 0x6
+ field public static final int PUBLISH_STATE_PUBLISHING = 7; // 0x7
field public static final int PUBLISH_STATE_RCS_PROVISION_ERROR = 4; // 0x4
field public static final int PUBLISH_STATE_REQUEST_TIMEOUT = 5; // 0x5
field public static final int PUBLISH_STATE_VOICE_PROVISION_ERROR = 3; // 0x3
@@ -14315,6 +14444,7 @@ package android.telephony.ims.feature {
package android.telephony.ims.stub {
public interface CapabilityExchangeEventListener {
+ method public default void onPublishUpdated(int, @NonNull String, int, @NonNull String) throws android.telephony.ims.ImsException;
method public void onRemoteCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.Set<java.lang.String>, @NonNull android.telephony.ims.stub.CapabilityExchangeEventListener.OptionsRequestCallback) throws android.telephony.ims.ImsException;
method public void onRequestPublishCapabilities(int) throws android.telephony.ims.ImsException;
method public void onUnpublish() throws android.telephony.ims.ImsException;
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 37de0c8a7ecb..a92150079ec9 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -685,6 +685,14 @@ package android.bluetooth {
}
+package android.companion {
+
+ public abstract class CompanionDeviceService extends android.app.Service {
+ method public void onBindCompanionDeviceService(@NonNull android.content.Intent);
+ }
+
+}
+
package android.content {
public final class AttributionSource implements android.os.Parcelable {
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index a642696b25ec..3573a5609bc6 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -1394,6 +1394,12 @@ public abstract class AccessibilityService extends Service {
* {@link AccessibilityService#onServiceConnected()} has not yet been
* called) or the service has been disconnected, this method will
* return a default value of {@code 1.0f}.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This legacy API gets the scale of full-screen
+ * magnification. To get the scale of the current controlling magnifier,
+ * use {@link #getMagnificationConfig} instead.
+ * </p>
*
* @return the current magnification scale
*/
@@ -1422,6 +1428,12 @@ public abstract class AccessibilityService extends Service {
* {@link AccessibilityService#onServiceConnected()} has not yet been
* called) or the service has been disconnected, this method will
* return a default value of {@code 0.0f}.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This legacy API gets the center position of full-screen
+ * magnification. To get the magnification center of the current controlling magnifier,
+ * use {@link #getMagnificationConfig} instead.
+ * </p>
*
* @return the unscaled screen-relative X coordinate of the center of
* the magnified region
@@ -1451,6 +1463,12 @@ public abstract class AccessibilityService extends Service {
* {@link AccessibilityService#onServiceConnected()} has not yet been
* called) or the service has been disconnected, this method will
* return a default value of {@code 0.0f}.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This legacy API gets the center position of full-screen
+ * magnification. To get the magnification center of the current controlling magnifier,
+ * use {@link #getMagnificationConfig} instead.
+ * </p>
*
* @return the unscaled screen-relative Y coordinate of the center of
* the magnified region
@@ -1571,6 +1589,11 @@ public abstract class AccessibilityService extends Service {
* {@link AccessibilityService#onServiceConnected()} has not yet been
* called) or the service has been disconnected, this method will have
* no effect and return {@code false}.
+ * <p>
+ * <strong>Note:</strong> This legacy API sets the scale of full-screen
+ * magnification. To set the scale of the specified magnifier,
+ * use {@link #setMagnificationConfig} instead.
+ * </p>
*
* @param scale the magnification scale to set, must be >= 1 and <= 8
* @param animate {@code true} to animate from the current scale or
@@ -1602,6 +1625,12 @@ public abstract class AccessibilityService extends Service {
* {@link AccessibilityService#onServiceConnected()} has not yet been
* called) or the service has been disconnected, this method will have
* no effect and return {@code false}.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This legacy API sets the center of full-screen
+ * magnification. To set the center of the specified magnifier,
+ * use {@link #setMagnificationConfig} instead.
+ * </p>
*
* @param centerX the unscaled screen-relative X coordinate on which to
* center the viewport
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 2c41e8d0925a..3cbae99224c7 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -23,6 +23,7 @@ import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Looper;
+import android.os.SystemProperties;
import android.os.Trace;
import android.util.AndroidRuntimeException;
import android.util.Log;
@@ -74,6 +75,8 @@ import java.util.HashMap;
public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {
private static final String TAG = "ValueAnimator";
private static final boolean DEBUG = false;
+ private static final boolean TRACE_ANIMATION_FRACTION = SystemProperties.getBoolean(
+ "persist.debug.animator.trace_fraction", false);
/**
* Internal constants
@@ -1554,6 +1557,10 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
@CallSuper
@UnsupportedAppUsage
void animateValue(float fraction) {
+ if (TRACE_ANIMATION_FRACTION) {
+ Trace.traceCounter(Trace.TRACE_TAG_VIEW, getNameForTrace() + hashCode(),
+ (int) (fraction * 1000));
+ }
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1c53fbd4de49..c0a8c1eb601e 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2700,6 +2700,16 @@ public final class ActivityThread extends ClientTransactionHandler
}
}
+ void onSystemUiContextCleanup(ContextImpl context) {
+ synchronized (this) {
+ if (mDisplaySystemUiContexts == null) return;
+ final int index = mDisplaySystemUiContexts.indexOfValue(context);
+ if (index >= 0) {
+ mDisplaySystemUiContexts.removeAt(index);
+ }
+ }
+ }
+
public void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {
synchronized (this) {
getSystemContext().installSystemApplicationInfo(info, classLoader);
@@ -4093,12 +4103,12 @@ public final class ActivityThread extends ClientTransactionHandler
}
private void handleStartBinderTracking() {
- Binder.enableTracing();
+ Binder.enableStackTracking();
}
private void handleStopBinderTrackingAndDump(ParcelFileDescriptor fd) {
try {
- Binder.disableTracing();
+ Binder.disableStackTracking();
Binder.getTransactionTracker().writeTracesToFile(fd);
} finally {
IoUtils.closeQuietly(fd);
@@ -6637,7 +6647,7 @@ public final class ActivityThread extends ClientTransactionHandler
boolean isAppProfileable = isAppDebuggable || data.appInfo.isProfileable();
Trace.setAppTracingAllowed(isAppProfileable);
if ((isAppProfileable || Build.IS_DEBUGGABLE) && data.enableBinderTracking) {
- Binder.enableTracing();
+ Binder.enableStackTracking();
}
// Initialize heap profiling.
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 4a7361efe4cc..885feb1f789a 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3212,6 +3212,10 @@ class ContextImpl extends Context {
final void performFinalCleanup(String who, String what) {
//Log.i(TAG, "Cleanup up context: " + this);
mPackageInfo.removeContextRegistrations(getOuterContext(), who, what);
+ if (mContextType == CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI
+ && mToken instanceof WindowTokenClient) {
+ mMainThread.onSystemUiContextCleanup(this);
+ }
}
@UnsupportedAppUsage
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 8f904b57474a..a2578d6d598a 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -520,6 +520,10 @@ interface IActivityManager {
// descriptor.
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
boolean stopBinderTrackingAndDump(in ParcelFileDescriptor fd);
+
+ /** Enables server-side binder tracing for the calling uid. */
+ void enableBinderTracing();
+
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
void suppressResizeConfigChanges(boolean suppress);
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index af907af2ce0d..779552f1bbed 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5808,6 +5808,7 @@ public class Notification implements Parcelable
p, result);
buildCustomContentIntoTemplate(mContext, standard, customContent,
p, result);
+ makeHeaderExpanded(standard);
return standard;
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 81e6ae453277..5002a59440e9 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -131,6 +131,7 @@ import android.media.tv.interactive.TvIAppManager;
import android.media.tv.tunerresourcemanager.ITunerResourceManager;
import android.media.tv.tunerresourcemanager.TunerResourceManager;
import android.net.ConnectivityFrameworkInitializer;
+import android.net.ConnectivityFrameworkInitializerTiramisu;
import android.net.EthernetManager;
import android.net.IEthernetManager;
import android.net.IIpSecService;
@@ -146,8 +147,6 @@ import android.net.TetheringManager;
import android.net.VpnManager;
import android.net.lowpan.ILowpanManager;
import android.net.lowpan.LowpanManager;
-import android.net.nsd.INsdManager;
-import android.net.nsd.NsdManager;
import android.net.vcn.IVcnManagementService;
import android.net.vcn.VcnManager;
import android.net.wifi.WifiFrameworkInitializer;
@@ -576,15 +575,6 @@ public final class SystemServiceRegistry {
ctx.mMainThread.getHandler());
}});
- registerService(Context.NSD_SERVICE, NsdManager.class,
- new CachedServiceFetcher<NsdManager>() {
- @Override
- public NsdManager createService(ContextImpl ctx) throws ServiceNotFoundException {
- IBinder b = ServiceManager.getServiceOrThrow(Context.NSD_SERVICE);
- INsdManager service = INsdManager.Stub.asInterface(b);
- return new NsdManager(ctx.getOuterContext(), service);
- }});
-
registerService(Context.PEOPLE_SERVICE, PeopleManager.class,
new CachedServiceFetcher<PeopleManager>() {
@Override
@@ -1547,6 +1537,7 @@ public final class SystemServiceRegistry {
SupplementalProcessFrameworkInitializer.registerServiceWrappers();
UwbFrameworkInitializer.registerServiceWrappers();
SafetyCenterFrameworkInitializer.registerServiceWrappers();
+ ConnectivityFrameworkInitializerTiramisu.registerServiceWrappers();
} finally {
// If any of the above code throws, we're in a pretty bad shape and the process
// will likely crash, but we'll reset it just in case there's an exception handler...
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 2b12f12a8ec0..ae1342593af7 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -776,6 +776,51 @@ public final class CompanionDeviceManager {
}
}
+ /**
+ * Notify the system that the given self-managed association has just 'appeared'.
+ * This causes the system to bind to the companion app to keep it running until the association
+ * is reported as 'disappeared'
+ *
+ * <p>This API is only available for the companion apps that manage the connectivity by
+ * themselves.</p>
+ *
+ * @param associationId the unique {@link AssociationInfo#getId ID} assigned to the Association
+ * recorded by CompanionDeviceManager
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED)
+ public void notifyDeviceAppeared(int associationId) {
+ try {
+ mService.notifyDeviceAppeared(associationId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Notify the system that the given self-managed association has just 'disappeared'.
+ * This causes the system to unbind to the companion app.
+ *
+ * <p>This API is only available for the companion apps that manage the connectivity by
+ * themselves.</p>
+ *
+ * @param associationId the unique {@link AssociationInfo#getId ID} assigned to the Association
+ * recorded by CompanionDeviceManager
+
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED)
+ public void notifyDeviceDisappeared(int associationId) {
+ try {
+ mService.notifyDeviceDisappeared(associationId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private boolean checkFeaturePresent() {
boolean featurePresent = mService != null;
if (!featurePresent && DEBUG) {
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index 15266d62a963..3237f7ca340a 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -21,12 +21,14 @@ import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
+
import com.android.internal.util.function.pooled.PooledLambda;
import java.util.Objects;
@@ -34,8 +36,9 @@ import java.util.Objects;
/**
* Service to be implemented by apps that manage a companion device.
*
- * System will keep this service bound whenever an associated device is nearby,
- * ensuring app stays alive.
+ * System will keep this service bound whenever an associated device is nearby for Bluetooth
+ * devices or companion app manages the connectivity and reports disappeared, ensuring app stays
+ * alive
*
* An app must be {@link CompanionDeviceManager#associate associated} with at leas one device,
* before it can take advantage of this service.
@@ -43,6 +46,17 @@ import java.util.Objects;
* You must declare this service in your manifest with an
* intent-filter action of {@link #SERVICE_INTERFACE} and
* permission of {@link android.Manifest.permission#BIND_COMPANION_DEVICE_SERVICE}
+ *
+ * <p>If you want to declare more than one of these services, you must declare the meta-data in the
+ * service of your manifest with the corresponding name and value to true to indicate the
+ * primary service.
+ * Only the primary one will get the callback from
+ * {@link #onDeviceAppeared(AssociationInfo associationInfo)}.</p>
+ *
+ * Example:
+ * <meta-data
+ * android:name="primary"
+ * android:value="true" />
*/
public abstract class CompanionDeviceService extends Service {
@@ -52,13 +66,14 @@ public abstract class CompanionDeviceService extends Service {
* An intent action for a service to be bound whenever this app's companion device(s)
* are nearby.
*
- * <p>The app will be kept alive for as long as the device is nearby.
+ * <p>The app will be kept alive for as long as the device is nearby or companion app reports
+ * appeared.
* If the app is not running at the time device gets connected, the app will be woken up.</p>
*
- * <p>Shortly after the device goes out of range, the service will be unbound, and the
- * app will be eligible for cleanup, unless any other user-visible components are running.</p>
+ * <p>Shortly after the device goes out of range or the companion app reports disappeared,
+ * the service will be unbound, and the app will be eligible for cleanup, unless any other
+ * user-visible components are running.</p>
*
- * <p>An app shouldn't declare more than one of these services.
* If running in background is not essential for the devices that this app can manage,
* app should avoid declaring this service.</p>
*
@@ -73,9 +88,13 @@ public abstract class CompanionDeviceService extends Service {
* Called by system whenever a device associated with this app is available.
*
* @param address the MAC address of the device
+ * @deprecated please override {@link #onDeviceAppeared(AssociationInfo)} instead.
*/
+ @Deprecated
@MainThread
- public abstract void onDeviceAppeared(@NonNull String address);
+ public void onDeviceAppeared(@NonNull String address) {
+ // Do nothing. Companion apps can override this function.
+ }
/**
* Called by system whenever a device associated with this app stops being available.
@@ -83,9 +102,13 @@ public abstract class CompanionDeviceService extends Service {
* Usually this means the device goes out of range or is turned off.
*
* @param address the MAC address of the device
+ * @deprecated please override {@link #onDeviceDisappeared(AssociationInfo)} instead.
*/
+ @Deprecated
@MainThread
- public abstract void onDeviceDisappeared(@NonNull String address);
+ public void onDeviceDisappeared(@NonNull String address) {
+ // Do nothing. Companion apps can override this function.
+ }
/**
* Called by system whenever the system dispatches a message to the app to send it to
@@ -118,10 +141,35 @@ public abstract class CompanionDeviceService extends Service {
companionDeviceManager.dispatchMessage(messageId, associationId, message);
}
+ /**
+ * Called by system whenever a device associated with this app is connected.
+ *
+ * @param associationInfo A record for the companion device.
+ */
+ @MainThread
+ public void onDeviceAppeared(@NonNull AssociationInfo associationInfo) {
+ if (!associationInfo.isSelfManaged()) {
+ onDeviceAppeared(associationInfo.getDeviceMacAddressAsString());
+ }
+ }
+
+ /**
+ * Called by system whenever a device associated with this app is disconnected.
+ *
+ * @param associationInfo A record for the companion device.
+ */
+ @MainThread
+ public void onDeviceDisappeared(@NonNull AssociationInfo associationInfo) {
+ if (!associationInfo.isSelfManaged()) {
+ onDeviceDisappeared(associationInfo.getDeviceMacAddressAsString());
+ }
+ }
+
@Nullable
@Override
public final IBinder onBind(@NonNull Intent intent) {
if (Objects.equals(intent.getAction(), SERVICE_INTERFACE)) {
+ onBindCompanionDeviceService(intent);
return mRemote;
}
Log.w(LOG_TAG,
@@ -129,20 +177,26 @@ public abstract class CompanionDeviceService extends Service {
return null;
}
+ /**
+ * Used to track the state of Binder connection in CTS tests.
+ * @hide
+ */
+ @TestApi
+ public void onBindCompanionDeviceService(@NonNull Intent intent) {
+ }
+
class Stub extends ICompanionDeviceService.Stub {
@Override
- public void onDeviceAppeared(String address) {
- Handler.getMain().sendMessage(PooledLambda.obtainMessage(
- CompanionDeviceService::onDeviceAppeared,
- CompanionDeviceService.this, address));
+ public void onDeviceAppeared(AssociationInfo associationInfo) {
+ Handler.getMain().post(
+ () -> CompanionDeviceService.this.onDeviceAppeared(associationInfo));
}
@Override
- public void onDeviceDisappeared(String address) {
- Handler.getMain().sendMessage(PooledLambda.obtainMessage(
- CompanionDeviceService::onDeviceDisappeared,
- CompanionDeviceService.this, address));
+ public void onDeviceDisappeared(AssociationInfo associationInfo) {
+ Handler.getMain().post(
+ () -> CompanionDeviceService.this.onDeviceDisappeared(associationInfo));
}
public void onDispatchMessage(int messageId, int associationId, @NonNull byte[] message) {
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 1558db22a003..68a6031f543f 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -65,5 +65,10 @@ interface ICompanionDeviceManager {
void dispatchMessage(in int messageId, in int associationId, in byte[] message);
void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId);
+
void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId);
+
+ void notifyDeviceAppeared(int associationId);
+
+ void notifyDeviceDisappeared(int associationId);
}
diff --git a/core/java/android/companion/ICompanionDeviceService.aidl b/core/java/android/companion/ICompanionDeviceService.aidl
index 25212a1f1030..4e453573f62e 100644
--- a/core/java/android/companion/ICompanionDeviceService.aidl
+++ b/core/java/android/companion/ICompanionDeviceService.aidl
@@ -16,9 +16,11 @@
package android.companion;
+import android.companion.AssociationInfo;
+
/** @hide */
oneway interface ICompanionDeviceService {
- void onDeviceAppeared(in String address);
- void onDeviceDisappeared(in String address);
+ void onDeviceAppeared(in AssociationInfo associationInfo);
+ void onDeviceDisappeared(in AssociationInfo associationInfo);
void onDispatchMessage(in int messageId, in int associationId, in byte[] message);
}
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index dabc603bc47f..82ad15057fe3 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -16,6 +16,13 @@
package android.companion.virtual;
+import android.graphics.Point;
+import android.hardware.input.VirtualKeyEvent;
+import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseRelativeEvent;
+import android.hardware.input.VirtualMouseScrollEvent;
+import android.hardware.input.VirtualTouchEvent;
+
/**
* Interface for a virtual device.
*
@@ -34,4 +41,29 @@ interface IVirtualDevice {
* Closes the virtual device and frees all associated resources.
*/
void close();
+ void createVirtualKeyboard(
+ int displayId,
+ String inputDeviceName,
+ int vendorId,
+ int productId,
+ IBinder token);
+ void createVirtualMouse(
+ int displayId,
+ String inputDeviceName,
+ int vendorId,
+ int productId,
+ IBinder token);
+ void createVirtualTouchscreen(
+ int displayId,
+ String inputDeviceName,
+ int vendorId,
+ int productId,
+ IBinder token,
+ in Point screenSize);
+ void unregisterInputDevice(IBinder token);
+ boolean sendKeyEvent(IBinder token, in VirtualKeyEvent event);
+ boolean sendButtonEvent(IBinder token, in VirtualMouseButtonEvent event);
+ boolean sendRelativeEvent(IBinder token, in VirtualMouseRelativeEvent event);
+ boolean sendScrollEvent(IBinder token, in VirtualMouseScrollEvent event);
+ boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event);
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 590b10887c7f..0d024b1d3200 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -23,7 +23,13 @@ import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.companion.AssociationInfo;
import android.content.Context;
+import android.graphics.Point;
+import android.hardware.display.VirtualDisplay;
+import android.hardware.input.VirtualKeyboard;
+import android.hardware.input.VirtualMouse;
+import android.hardware.input.VirtualTouchscreen;
import android.os.Binder;
+import android.os.IBinder;
import android.os.RemoteException;
/**
@@ -73,6 +79,8 @@ public final class VirtualDeviceManager {
* A virtual device has its own virtual display, audio output, microphone, and camera 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.
+ *
+ * TODO(b/204081582): Consider using a builder pattern for the input APIs.
*/
public static class VirtualDevice implements AutoCloseable {
@@ -95,5 +103,88 @@ public final class VirtualDeviceManager {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * 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 vendor id
+ * @param productId the product id
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @NonNull
+ public VirtualKeyboard createVirtualKeyboard(
+ @NonNull VirtualDisplay display,
+ @NonNull String inputDeviceName,
+ int vendorId,
+ int productId) {
+ try {
+ final IBinder token = new Binder(
+ "android.hardware.input.VirtualKeyboard:" + inputDeviceName);
+ mVirtualDevice.createVirtualKeyboard(display.getDisplay().getDisplayId(),
+ inputDeviceName, vendorId, productId, token);
+ return new VirtualKeyboard(mVirtualDevice, token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * 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 vendor id
+ * @param productId the product id
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @NonNull
+ public VirtualMouse createVirtualMouse(
+ @NonNull VirtualDisplay display,
+ @NonNull String inputDeviceName,
+ int vendorId,
+ int productId) {
+ try {
+ final IBinder token = new Binder(
+ "android.hardware.input.VirtualMouse:" + inputDeviceName);
+ mVirtualDevice.createVirtualMouse(display.getDisplay().getDisplayId(),
+ inputDeviceName, vendorId, productId, token);
+ return new VirtualMouse(mVirtualDevice, token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * 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 vendor id
+ * @param productId the product id
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @NonNull
+ public VirtualTouchscreen createVirtualTouchscreen(
+ @NonNull VirtualDisplay display,
+ @NonNull String inputDeviceName,
+ int vendorId,
+ int productId) {
+ try {
+ final IBinder token = new Binder(
+ "android.hardware.input.VirtualTouchscreen:" + inputDeviceName);
+ final Point size = new Point();
+ display.getDisplay().getSize(size);
+ mVirtualDevice.createVirtualTouchscreen(display.getDisplay().getDisplayId(),
+ inputDeviceName, vendorId, productId, token, size);
+ return new VirtualTouchscreen(mVirtualDevice, token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 632d8e5e291c..b5d4f09144ee 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -80,6 +80,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
@@ -1942,6 +1943,16 @@ public class Resources {
final Theme other = (Theme) o;
return getKey().equals(other.getKey());
}
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append('{');
+ sb.append("id=0x").append(Integer.toHexString(getAppliedStyleResId())).append(", ");
+ sb.append("themes=").append(Arrays.deepToString(getTheme()));
+ sb.append('}');
+ return sb.toString();
+ }
}
static class ThemeKey implements Cloneable {
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index d6f55d65ead5..9eb9cd51284a 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -527,11 +527,12 @@ public class TypedArray implements AutoCloseable {
final TypedValue value = mValue;
getValueAt(index, value);
throw new UnsupportedOperationException(
- "Failed to resolve attribute at index " + attrIndex + ": " + value);
+ "Failed to resolve attribute at index " + attrIndex + ": " + value
+ + ", theme=" + mTheme);
}
throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
- + " to color: type=0x" + Integer.toHexString(type));
+ + " to color: type=0x" + Integer.toHexString(type) + ", theme=" + mTheme);
}
/**
@@ -561,7 +562,8 @@ public class TypedArray implements AutoCloseable {
if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
- "Failed to resolve attribute at index " + index + ": " + value);
+ "Failed to resolve attribute at index " + index + ": " + value
+ + ", theme=" + mTheme);
}
return mResources.loadComplexColor(value, value.resourceId, mTheme);
}
@@ -596,7 +598,8 @@ public class TypedArray implements AutoCloseable {
if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
- "Failed to resolve attribute at index " + index + ": " + value);
+ "Failed to resolve attribute at index " + index + ": " + value
+ + ", theme=" + mTheme);
}
return mResources.loadColorStateList(value, value.resourceId, mTheme);
}
@@ -637,11 +640,12 @@ public class TypedArray implements AutoCloseable {
final TypedValue value = mValue;
getValueAt(index, value);
throw new UnsupportedOperationException(
- "Failed to resolve attribute at index " + attrIndex + ": " + value);
+ "Failed to resolve attribute at index " + attrIndex + ": " + value
+ + ", theme=" + mTheme);
}
throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
- + " to integer: type=0x" + Integer.toHexString(type));
+ + " to integer: type=0x" + Integer.toHexString(type) + ", theme=" + mTheme);
}
/**
@@ -684,11 +688,12 @@ public class TypedArray implements AutoCloseable {
final TypedValue value = mValue;
getValueAt(index, value);
throw new UnsupportedOperationException(
- "Failed to resolve attribute at index " + attrIndex + ": " + value);
+ "Failed to resolve attribute at index " + attrIndex + ": " + value
+ + ", theme=" + mTheme);
}
throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
- + " to dimension: type=0x" + Integer.toHexString(type));
+ + " to dimension: type=0x" + Integer.toHexString(type) + ", theme=" + mTheme);
}
/**
@@ -732,11 +737,12 @@ public class TypedArray implements AutoCloseable {
final TypedValue value = mValue;
getValueAt(index, value);
throw new UnsupportedOperationException(
- "Failed to resolve attribute at index " + attrIndex + ": " + value);
+ "Failed to resolve attribute at index " + attrIndex + ": " + value
+ + ", theme=" + mTheme);
}
throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
- + " to dimension: type=0x" + Integer.toHexString(type));
+ + " to dimension: type=0x" + Integer.toHexString(type) + ", theme=" + mTheme);
}
/**
@@ -781,11 +787,12 @@ public class TypedArray implements AutoCloseable {
final TypedValue value = mValue;
getValueAt(index, value);
throw new UnsupportedOperationException(
- "Failed to resolve attribute at index " + attrIndex + ": " + value);
+ "Failed to resolve attribute at index " + attrIndex + ": " + value
+ + ", theme=" + mTheme);
}
throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
- + " to dimension: type=0x" + Integer.toHexString(type));
+ + " to dimension: type=0x" + Integer.toHexString(type) + ", theme=" + mTheme);
}
/**
@@ -825,11 +832,12 @@ public class TypedArray implements AutoCloseable {
final TypedValue value = mValue;
getValueAt(index, value);
throw new UnsupportedOperationException(
- "Failed to resolve attribute at index " + attrIndex + ": " + value);
+ "Failed to resolve attribute at index " + attrIndex + ": " + value
+ + ", theme=" + mTheme);
}
throw new UnsupportedOperationException(getPositionDescription()
- + ": You must supply a " + name + " attribute.");
+ + ": You must supply a " + name + " attribute." + ", theme=" + mTheme);
}
/**
@@ -900,11 +908,12 @@ public class TypedArray implements AutoCloseable {
final TypedValue value = mValue;
getValueAt(index, value);
throw new UnsupportedOperationException(
- "Failed to resolve attribute at index " + attrIndex + ": " + value);
+ "Failed to resolve attribute at index " + attrIndex + ": " + value
+ + ", theme=" + mTheme);
}
throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
- + " to fraction: type=0x" + Integer.toHexString(type));
+ + " to fraction: type=0x" + Integer.toHexString(type) + ", theme=" + mTheme);
}
/**
@@ -996,7 +1005,8 @@ public class TypedArray implements AutoCloseable {
if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
- "Failed to resolve attribute at index " + index + ": " + value);
+ "Failed to resolve attribute at index " + index + ": " + value
+ + ", theme=" + mTheme);
}
if (density > 0) {
@@ -1032,7 +1042,8 @@ public class TypedArray implements AutoCloseable {
if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
- "Failed to resolve attribute at index " + index + ": " + value);
+ "Failed to resolve attribute at index " + index + ": " + value
+ + ", theme=" + mTheme);
}
return mResources.getFont(value, value.resourceId);
}
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index cad30dda9034..a4a8f313e3ba 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -25,6 +25,7 @@ import android.graphics.GraphicBuffer;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.SurfaceControl;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -129,6 +130,15 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable {
public static final long USAGE_GPU_SAMPLED_IMAGE = 1 << 8;
/** Usage: The buffer will be written to by the GPU */
public static final long USAGE_GPU_COLOR_OUTPUT = 1 << 9;
+ /**
+ * The buffer will be used as a composer HAL overlay layer.
+ *
+ * This flag is currently only needed when using
+ * {@link android.view.SurfaceControl.Transaction#setBuffer(SurfaceControl, HardwareBuffer)}
+ * to set a buffer. In all other cases, the framework adds this flag
+ * internally to buffers that could be presented in a composer overlay.
+ */
+ public static final long USAGE_COMPOSER_OVERLAY = 1 << 11;
/** Usage: The buffer must not be used outside of a protected hardware path */
public static final long USAGE_PROTECTED_CONTENT = 1 << 14;
/** Usage: The buffer will be read by a hardware video encoder */
diff --git a/core/java/android/hardware/input/VirtualKeyEvent.aidl b/core/java/android/hardware/input/VirtualKeyEvent.aidl
new file mode 100644
index 000000000000..5b3ee0c985bd
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualKeyEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+parcelable VirtualKeyEvent; \ No newline at end of file
diff --git a/core/java/android/hardware/input/VirtualKeyEvent.java b/core/java/android/hardware/input/VirtualKeyEvent.java
new file mode 100644
index 000000000000..d875156f5dc7
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualKeyEvent.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.KeyEvent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An event describing a keyboard interaction originating from a remote device.
+ *
+ * When the user presses a key, an {@code ACTION_DOWN} event should be reported. When the user
+ * releases the key, an {@code ACTION_UP} event should be reported.
+ *
+ * See {@link android.view.KeyEvent}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualKeyEvent implements Parcelable {
+
+ /** @hide */
+ public static final int ACTION_UNKNOWN = -1;
+ /** Action indicating the given key has been pressed. */
+ public static final int ACTION_DOWN = KeyEvent.ACTION_DOWN;
+ /** Action indicating the previously pressed key has been lifted. */
+ public static final int ACTION_UP = KeyEvent.ACTION_UP;
+
+ /** @hide */
+ @IntDef(prefix = { "ACTION_" }, value = {
+ ACTION_UNKNOWN,
+ ACTION_DOWN,
+ ACTION_UP,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Action {
+ }
+
+ private final @Action int mAction;
+ private final int mKeyCode;
+
+ private VirtualKeyEvent(@Action int action, int keyCode) {
+ mAction = action;
+ mKeyCode = keyCode;
+ }
+
+ private VirtualKeyEvent(@NonNull Parcel parcel) {
+ mAction = parcel.readInt();
+ mKeyCode = parcel.readInt();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) {
+ parcel.writeInt(mAction);
+ parcel.writeInt(mKeyCode);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Returns the key code associated with this event.
+ */
+ public int getKeyCode() {
+ return mKeyCode;
+ }
+
+ /**
+ * Returns the action associated with this event.
+ */
+ public @Action int getAction() {
+ return mAction;
+ }
+
+ /**
+ * Builder for {@link VirtualKeyEvent}.
+ */
+ public static final class Builder {
+
+ private @Action int mAction = ACTION_UNKNOWN;
+ private int mKeyCode = -1;
+
+ /**
+ * Creates a {@link VirtualKeyEvent} object with the current builder configuration.
+ */
+ public @NonNull VirtualKeyEvent build() {
+ if (mAction == ACTION_UNKNOWN || mKeyCode == -1) {
+ throw new IllegalArgumentException(
+ "Cannot build virtual key event with unset fields");
+ }
+ return new VirtualKeyEvent(mAction, mKeyCode);
+ }
+
+ /**
+ * Sets the Android key code of the event. The set of allowed characters include digits 0-9,
+ * characters A-Z, and standard punctuation, as well as numpad keys, function keys F1-F12,
+ * and meta keys (caps lock, shift, etc.).
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ public @NonNull Builder setKeyCode(int keyCode) {
+ mKeyCode = keyCode;
+ return this;
+ }
+
+ /**
+ * Sets the action of the event.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ public @NonNull Builder setAction(@Action int action) {
+ if (action != ACTION_DOWN && action != ACTION_UP) {
+ throw new IllegalArgumentException("Unsupported action type");
+ }
+ mAction = action;
+ return this;
+ }
+ }
+
+ public static final @NonNull Parcelable.Creator<VirtualKeyEvent> CREATOR =
+ new Parcelable.Creator<VirtualKeyEvent>() {
+ public VirtualKeyEvent createFromParcel(Parcel source) {
+ return new VirtualKeyEvent(source);
+ }
+
+ public VirtualKeyEvent[] newArray(int size) {
+ return new VirtualKeyEvent[size];
+ }
+ };
+}
diff --git a/core/java/android/hardware/input/VirtualKeyboard.java b/core/java/android/hardware/input/VirtualKeyboard.java
new file mode 100644
index 000000000000..ee9b659e9521
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualKeyboard.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.companion.virtual.IVirtualDevice;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.io.Closeable;
+
+/**
+ * A virtual keyboard representing a key input mechanism on a remote device, such as a built-in
+ * keyboard on a laptop, a software keyboard on a tablet, or a keypad on a TV remote control.
+ *
+ * This registers an InputDevice that is interpreted like a physically-connected device and
+ * dispatches received events to it.
+ *
+ * @hide
+ */
+@SystemApi
+public class VirtualKeyboard implements Closeable {
+
+ private final IVirtualDevice mVirtualDevice;
+ private final IBinder mToken;
+
+ /** @hide */
+ public VirtualKeyboard(IVirtualDevice virtualDevice, IBinder token) {
+ mVirtualDevice = virtualDevice;
+ mToken = token;
+ }
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void close() {
+ try {
+ mVirtualDevice.unregisterInputDevice(mToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sends a key event to the system.
+ *
+ * @param event the event to send
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void sendKeyEvent(@NonNull VirtualKeyEvent event) {
+ try {
+ mVirtualDevice.sendKeyEvent(mToken, event);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/hardware/input/VirtualMouse.java b/core/java/android/hardware/input/VirtualMouse.java
new file mode 100644
index 000000000000..6599dd2e28eb
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualMouse.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.companion.virtual.IVirtualDevice;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.MotionEvent;
+
+import java.io.Closeable;
+
+/**
+ * A virtual mouse representing a relative input mechanism on a remote device, such as a mouse or
+ * trackpad.
+ *
+ * This registers an InputDevice that is interpreted like a physically-connected device and
+ * dispatches received events to it.
+ *
+ * @hide
+ */
+@SystemApi
+public class VirtualMouse implements Closeable {
+
+ private final IVirtualDevice mVirtualDevice;
+ private final IBinder mToken;
+
+ /** @hide */
+ public VirtualMouse(IVirtualDevice virtualDevice, IBinder token) {
+ mVirtualDevice = virtualDevice;
+ mToken = token;
+ }
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void close() {
+ try {
+ mVirtualDevice.unregisterInputDevice(mToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Send a mouse button event to the system.
+ *
+ * @param event the event
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void sendButtonEvent(@NonNull VirtualMouseButtonEvent event) {
+ try {
+ mVirtualDevice.sendButtonEvent(mToken, event);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sends a scrolling event to the system. See {@link MotionEvent#AXIS_VSCROLL} and
+ * {@link MotionEvent#AXIS_SCROLL}.
+ *
+ * @param event the event
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void sendScrollEvent(@NonNull VirtualMouseScrollEvent event) {
+ try {
+ mVirtualDevice.sendScrollEvent(mToken, event);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sends a relative movement event to the system.
+ *
+ * @param event the event
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void sendRelativeEvent(@NonNull VirtualMouseRelativeEvent event) {
+ try {
+ mVirtualDevice.sendRelativeEvent(mToken, event);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/hardware/input/VirtualMouseButtonEvent.aidl b/core/java/android/hardware/input/VirtualMouseButtonEvent.aidl
new file mode 100644
index 000000000000..ebcf5aad4066
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualMouseButtonEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+parcelable VirtualMouseButtonEvent; \ No newline at end of file
diff --git a/core/java/android/hardware/input/VirtualMouseButtonEvent.java b/core/java/android/hardware/input/VirtualMouseButtonEvent.java
new file mode 100644
index 000000000000..2e094cfb4e24
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualMouseButtonEvent.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.MotionEvent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An event describing a mouse button click interaction originating from a remote device.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualMouseButtonEvent implements Parcelable {
+
+ /** @hide */
+ public static final int ACTION_UNKNOWN = -1;
+ /** Action indicating the mouse button has been pressed. */
+ public static final int ACTION_BUTTON_PRESS = MotionEvent.ACTION_BUTTON_PRESS;
+ /** Action indicating the mouse button has been released. */
+ public static final int ACTION_BUTTON_RELEASE = MotionEvent.ACTION_BUTTON_RELEASE;
+ /** @hide */
+ @IntDef(prefix = {"ACTION_"}, value = {
+ ACTION_UNKNOWN,
+ ACTION_BUTTON_PRESS,
+ ACTION_BUTTON_RELEASE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Action {}
+
+ /** @hide */
+ public static final int BUTTON_UNKNOWN = -1;
+ /** Action indicating the mouse button involved in this event is in the left position. */
+ public static final int BUTTON_PRIMARY = MotionEvent.BUTTON_PRIMARY;
+ /** Action indicating the mouse button involved in this event is in the middle position. */
+ public static final int BUTTON_TERTIARY = MotionEvent.BUTTON_TERTIARY;
+ /** Action indicating the mouse button involved in this event is in the right position. */
+ public static final int BUTTON_SECONDARY = MotionEvent.BUTTON_SECONDARY;
+ /**
+ * Action indicating the mouse button involved in this event is intended to go back to the
+ * previous.
+ */
+ public static final int BUTTON_BACK = MotionEvent.BUTTON_BACK;
+ /**
+ * Action indicating the mouse button involved in this event is intended to move forward to the
+ * next.
+ */
+ public static final int BUTTON_FORWARD = MotionEvent.BUTTON_FORWARD;
+ /** @hide */
+ @IntDef(prefix = {"BUTTON_"}, value = {
+ BUTTON_UNKNOWN,
+ BUTTON_PRIMARY,
+ BUTTON_TERTIARY,
+ BUTTON_SECONDARY,
+ BUTTON_BACK,
+ BUTTON_FORWARD,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Button {}
+
+ private final @Action int mAction;
+ private final @Button int mButtonCode;
+
+ private VirtualMouseButtonEvent(@Action int action, @Button int buttonCode) {
+ mAction = action;
+ mButtonCode = buttonCode;
+ }
+
+ private VirtualMouseButtonEvent(@NonNull Parcel parcel) {
+ mAction = parcel.readInt();
+ mButtonCode = parcel.readInt();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) {
+ parcel.writeInt(mAction);
+ parcel.writeInt(mButtonCode);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Returns the button code associated with this event.
+ */
+ public @Button int getButtonCode() {
+ return mButtonCode;
+ }
+
+ /**
+ * Returns the action associated with this event.
+ */
+ public @Action int getAction() {
+ return mAction;
+ }
+
+ /**
+ * Builder for {@link VirtualMouseButtonEvent}.
+ */
+ public static final class Builder {
+
+ private @Action int mAction = ACTION_UNKNOWN;
+ private @Button int mButtonCode = -1;
+
+ /**
+ * Creates a {@link VirtualMouseButtonEvent} object with the current builder configuration.
+ */
+ public @NonNull VirtualMouseButtonEvent build() {
+ if (mAction == ACTION_UNKNOWN || mButtonCode == -1) {
+ throw new IllegalArgumentException(
+ "Cannot build virtual mouse button event with unset fields");
+ }
+ return new VirtualMouseButtonEvent(mAction, mButtonCode);
+ }
+
+ /**
+ * Sets the button code of the event.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ public @NonNull Builder setButtonCode(int buttonCode) {
+ if (buttonCode != BUTTON_PRIMARY
+ && buttonCode != BUTTON_TERTIARY
+ && buttonCode != BUTTON_SECONDARY
+ && buttonCode != BUTTON_BACK
+ && buttonCode != BUTTON_FORWARD) {
+ throw new IllegalArgumentException("Unsupported mouse button code");
+ }
+ mButtonCode = buttonCode;
+ return this;
+ }
+
+ /**
+ * Sets the action of the event.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ public @NonNull Builder setAction(@Action int action) {
+ if (action != ACTION_BUTTON_PRESS && action != ACTION_BUTTON_RELEASE) {
+ throw new IllegalArgumentException("Unsupported mouse button action type");
+ }
+ mAction = action;
+ return this;
+ }
+ }
+
+ public static final @NonNull Parcelable.Creator<VirtualMouseButtonEvent> CREATOR =
+ new Parcelable.Creator<VirtualMouseButtonEvent>() {
+ public VirtualMouseButtonEvent createFromParcel(Parcel source) {
+ return new VirtualMouseButtonEvent(source);
+ }
+
+ public VirtualMouseButtonEvent[] newArray(int size) {
+ return new VirtualMouseButtonEvent[size];
+ }
+ };
+}
diff --git a/core/java/android/hardware/input/VirtualMouseRelativeEvent.aidl b/core/java/android/hardware/input/VirtualMouseRelativeEvent.aidl
new file mode 100644
index 000000000000..1095858fde21
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualMouseRelativeEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+parcelable VirtualMouseRelativeEvent; \ No newline at end of file
diff --git a/core/java/android/hardware/input/VirtualMouseRelativeEvent.java b/core/java/android/hardware/input/VirtualMouseRelativeEvent.java
new file mode 100644
index 000000000000..65ed1f2f6f3a
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualMouseRelativeEvent.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * An event describing a mouse movement interaction originating from a remote device.
+ *
+ * See {@link android.view.MotionEvent}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualMouseRelativeEvent implements Parcelable {
+
+ private final float mRelativeX;
+ private final float mRelativeY;
+
+ private VirtualMouseRelativeEvent(float relativeX, float relativeY) {
+ mRelativeX = relativeX;
+ mRelativeY = relativeY;
+ }
+
+ private VirtualMouseRelativeEvent(@NonNull Parcel parcel) {
+ mRelativeX = parcel.readFloat();
+ mRelativeY = parcel.readFloat();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) {
+ parcel.writeFloat(mRelativeX);
+ parcel.writeFloat(mRelativeY);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Returns the relative x-axis movement, in pixels.
+ */
+ public float getRelativeX() {
+ return mRelativeX;
+ }
+
+ /**
+ * Returns the relative x-axis movement, in pixels.
+ */
+ public float getRelativeY() {
+ return mRelativeY;
+ }
+
+ /**
+ * Builder for {@link VirtualMouseRelativeEvent}.
+ */
+ public static final class Builder {
+
+ private float mRelativeX;
+ private float mRelativeY;
+
+ /**
+ * Creates a {@link VirtualMouseRelativeEvent} object with the current builder
+ * configuration.
+ */
+ public @NonNull VirtualMouseRelativeEvent build() {
+ return new VirtualMouseRelativeEvent(mRelativeX, mRelativeY);
+ }
+
+ /**
+ * Sets the relative x-axis movement, in pixels.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ public @NonNull Builder setRelativeX(float relativeX) {
+ mRelativeX = relativeX;
+ return this;
+ }
+
+ /**
+ * Sets the relative y-axis movement, in pixels.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ public @NonNull Builder setRelativeY(float relativeY) {
+ mRelativeY = relativeY;
+ return this;
+ }
+ }
+
+ public static final @NonNull Parcelable.Creator<VirtualMouseRelativeEvent> CREATOR =
+ new Parcelable.Creator<VirtualMouseRelativeEvent>() {
+ public VirtualMouseRelativeEvent createFromParcel(Parcel source) {
+ return new VirtualMouseRelativeEvent(source);
+ }
+
+ public VirtualMouseRelativeEvent[] newArray(int size) {
+ return new VirtualMouseRelativeEvent[size];
+ }
+ };
+}
diff --git a/core/java/android/hardware/input/VirtualMouseScrollEvent.aidl b/core/java/android/hardware/input/VirtualMouseScrollEvent.aidl
new file mode 100644
index 000000000000..13177efcbb62
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualMouseScrollEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+parcelable VirtualMouseScrollEvent; \ No newline at end of file
diff --git a/core/java/android/hardware/input/VirtualMouseScrollEvent.java b/core/java/android/hardware/input/VirtualMouseScrollEvent.java
new file mode 100644
index 000000000000..1723259ba4b7
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualMouseScrollEvent.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * An event describing a mouse scroll interaction originating from a remote device.
+ *
+ * See {@link android.view.MotionEvent}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualMouseScrollEvent implements Parcelable {
+
+ private final float mXAxisMovement;
+ private final float mYAxisMovement;
+
+ private VirtualMouseScrollEvent(float xAxisMovement, float yAxisMovement) {
+ mXAxisMovement = xAxisMovement;
+ mYAxisMovement = yAxisMovement;
+ }
+
+ private VirtualMouseScrollEvent(@NonNull Parcel parcel) {
+ mXAxisMovement = parcel.readFloat();
+ mYAxisMovement = parcel.readFloat();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) {
+ parcel.writeFloat(mXAxisMovement);
+ parcel.writeFloat(mYAxisMovement);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Returns the x-axis scroll movement, normalized from -1.0 to 1.0, inclusive. Positive values
+ * indicate scrolling upward; negative values, downward.
+ */
+ public float getXAxisMovement() {
+ return mXAxisMovement;
+ }
+
+ /**
+ * Returns the y-axis scroll movement, normalized from -1.0 to 1.0, inclusive. Positive values
+ * indicate scrolling towards the right; negative values, to the left.
+ */
+ public float getYAxisMovement() {
+ return mYAxisMovement;
+ }
+
+ /**
+ * Builder for {@link VirtualMouseScrollEvent}.
+ */
+ public static final class Builder {
+
+ private float mXAxisMovement;
+ private float mYAxisMovement;
+
+ /**
+ * Creates a {@link VirtualMouseScrollEvent} object with the current builder configuration.
+ */
+ public @NonNull VirtualMouseScrollEvent build() {
+ return new VirtualMouseScrollEvent(mXAxisMovement, mYAxisMovement);
+ }
+
+ /**
+ * Sets the x-axis scroll movement, normalized from -1.0 to 1.0, inclusive. Positive values
+ * indicate scrolling upward; negative values, downward.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ public @NonNull Builder setXAxisMovement(
+ @FloatRange(from = -1.0f, to = 1.0f) float xAxisMovement) {
+ Preconditions.checkArgumentInRange(xAxisMovement, -1f, 1f, "xAxisMovement");
+ mXAxisMovement = xAxisMovement;
+ return this;
+ }
+
+ /**
+ * Sets the y-axis scroll movement, normalized from -1.0 to 1.0, inclusive. Positive values
+ * indicate scrolling towards the right; negative values, to the left.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ public @NonNull Builder setYAxisMovement(
+ @FloatRange(from = -1.0f, to = 1.0f) float yAxisMovement) {
+ Preconditions.checkArgumentInRange(yAxisMovement, -1f, 1f, "yAxisMovement");
+ mYAxisMovement = yAxisMovement;
+ return this;
+ }
+ }
+
+ public static final @NonNull Parcelable.Creator<VirtualMouseScrollEvent> CREATOR =
+ new Parcelable.Creator<VirtualMouseScrollEvent>() {
+ public VirtualMouseScrollEvent createFromParcel(Parcel source) {
+ return new VirtualMouseScrollEvent(source);
+ }
+
+ public VirtualMouseScrollEvent[] newArray(int size) {
+ return new VirtualMouseScrollEvent[size];
+ }
+ };
+}
diff --git a/core/java/android/hardware/input/VirtualTouchEvent.aidl b/core/java/android/hardware/input/VirtualTouchEvent.aidl
new file mode 100644
index 000000000000..03c82e3eef3a
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualTouchEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+parcelable VirtualTouchEvent; \ No newline at end of file
diff --git a/core/java/android/hardware/input/VirtualTouchEvent.java b/core/java/android/hardware/input/VirtualTouchEvent.java
new file mode 100644
index 000000000000..c7450d8fa65d
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualTouchEvent.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.MotionEvent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An event describing a touchscreen interaction originating from a remote device.
+ *
+ * The pointer id, tool type, action, and location are required; pressure and main axis size are
+ * optional.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualTouchEvent implements Parcelable {
+
+ /** @hide */
+ public static final int TOOL_TYPE_UNKNOWN = MotionEvent.TOOL_TYPE_UNKNOWN;
+ /** Tool type indicating that the user's finger is the origin of the event. */
+ public static final int TOOL_TYPE_FINGER = MotionEvent.TOOL_TYPE_FINGER;
+ /**
+ * Tool type indicating that a user's palm (or other input mechanism to be rejected) is the
+ * origin of the event.
+ */
+ public static final int TOOL_TYPE_PALM = MotionEvent.TOOL_TYPE_PALM;
+ /** @hide */
+ @IntDef(prefix = { "TOOL_TYPE_" }, value = {
+ TOOL_TYPE_UNKNOWN,
+ TOOL_TYPE_FINGER,
+ TOOL_TYPE_PALM,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ToolType {}
+
+ /** @hide */
+ public static final int ACTION_UNKNOWN = -1;
+ /** Action indicating the tool has been pressed down to the touchscreen. */
+ public static final int ACTION_DOWN = MotionEvent.ACTION_DOWN;
+ /** Action indicating the tool has been lifted from the touchscreen. */
+ public static final int ACTION_UP = MotionEvent.ACTION_UP;
+ /** Action indicating the tool has been moved along the face of the touchscreen. */
+ public static final int ACTION_MOVE = MotionEvent.ACTION_MOVE;
+ /** Action indicating the tool cancelled the current movement. */
+ public static final int ACTION_CANCEL = MotionEvent.ACTION_CANCEL;
+ /** @hide */
+ @IntDef(prefix = { "ACTION_" }, value = {
+ ACTION_UNKNOWN,
+ ACTION_DOWN,
+ ACTION_UP,
+ ACTION_MOVE,
+ ACTION_CANCEL,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Action {}
+
+ private final int mPointerId;
+ private final @ToolType int mToolType;
+ private final @Action int mAction;
+ private final float mX;
+ private final float mY;
+ private final float mPressure;
+ private final float mMajorAxisSize;
+
+ private VirtualTouchEvent(int pointerId, @ToolType int toolType, @Action int action,
+ float x, float y, float pressure, float majorAxisSize) {
+ mPointerId = pointerId;
+ mToolType = toolType;
+ mAction = action;
+ mX = x;
+ mY = y;
+ mPressure = pressure;
+ mMajorAxisSize = majorAxisSize;
+ }
+
+ private VirtualTouchEvent(@NonNull Parcel parcel) {
+ mPointerId = parcel.readInt();
+ mToolType = parcel.readInt();
+ mAction = parcel.readInt();
+ mX = parcel.readFloat();
+ mY = parcel.readFloat();
+ mPressure = parcel.readFloat();
+ mMajorAxisSize = parcel.readFloat();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mPointerId);
+ dest.writeInt(mToolType);
+ dest.writeInt(mAction);
+ dest.writeFloat(mX);
+ dest.writeFloat(mY);
+ dest.writeFloat(mPressure);
+ dest.writeFloat(mMajorAxisSize);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Returns the pointer id associated with this event.
+ */
+ public int getPointerId() {
+ return mPointerId;
+ }
+
+ /**
+ * Returns the tool type associated with this event.
+ */
+ public @ToolType int getToolType() {
+ return mToolType;
+ }
+
+ /**
+ * Returns the action associated with this event.
+ */
+ public @Action int getAction() {
+ return mAction;
+ }
+
+ /**
+ * Returns the x-axis location associated with this event.
+ */
+ public float getX() {
+ return mX;
+ }
+
+ /**
+ * Returns the y-axis location associated with this event.
+ */
+ public float getY() {
+ return mY;
+ }
+
+ /**
+ * Returns the pressure associated with this event. Returns {@link Float#NaN} if omitted.
+ */
+ public float getPressure() {
+ return mPressure;
+ }
+
+ /**
+ * Returns the major axis size associated with this event. Returns {@link Float#NaN} if omitted.
+ */
+ public float getMajorAxisSize() {
+ return mMajorAxisSize;
+ }
+
+ /**
+ * Builder for {@link VirtualTouchEvent}.
+ */
+ public static final class Builder {
+
+ private @ToolType int mToolType = TOOL_TYPE_UNKNOWN;
+ private int mPointerId = MotionEvent.INVALID_POINTER_ID;
+ private @Action int mAction = ACTION_UNKNOWN;
+ private float mX = Float.NaN;
+ private float mY = Float.NaN;
+ private float mPressure = Float.NaN;
+ private float mMajorAxisSize = Float.NaN;
+
+ /**
+ * Creates a {@link VirtualTouchEvent} object with the current builder configuration.
+ */
+ public @NonNull VirtualTouchEvent build() {
+ if (mToolType == TOOL_TYPE_UNKNOWN || mPointerId == MotionEvent.INVALID_POINTER_ID
+ || mAction == ACTION_UNKNOWN || Float.isNaN(mX) || Float.isNaN(mY)) {
+ throw new IllegalArgumentException(
+ "Cannot build virtual touch event with unset required fields");
+ }
+ if ((mToolType == TOOL_TYPE_PALM && mAction != ACTION_CANCEL)
+ || (mAction == ACTION_CANCEL && mToolType != TOOL_TYPE_PALM)) {
+ throw new IllegalArgumentException(
+ "ACTION_CANCEL and TOOL_TYPE_PALM must always appear together");
+ }
+ return new VirtualTouchEvent(mPointerId, mToolType, mAction, mX, mY, mPressure,
+ mMajorAxisSize);
+ }
+
+ /**
+ * Sets the pointer id of the event.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ public @NonNull Builder setPointerId(int pointerId) {
+ mPointerId = pointerId;
+ return this;
+ }
+
+ /**
+ * Sets the tool type of the event.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ public @NonNull Builder setToolType(@ToolType int toolType) {
+ if (toolType != TOOL_TYPE_FINGER && toolType != TOOL_TYPE_PALM) {
+ throw new IllegalArgumentException("Unsupported touch event tool type");
+ }
+ mToolType = toolType;
+ return this;
+ }
+
+ /**
+ * Sets the action of the event.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ public @NonNull Builder setAction(@Action int action) {
+ if (action != ACTION_DOWN && action != ACTION_UP && action != ACTION_MOVE
+ && action != ACTION_CANCEL) {
+ throw new IllegalArgumentException("Unsupported touch event action type");
+ }
+ mAction = action;
+ return this;
+ }
+
+ /**
+ * Sets the x-axis location of the event.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ public @NonNull Builder setX(float absX) {
+ mX = absX;
+ return this;
+ }
+
+ /**
+ * Sets the y-axis location of the event.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ public @NonNull Builder setY(float absY) {
+ mY = absY;
+ return this;
+ }
+
+ /**
+ * Sets the pressure of the event. This field is optional and can be omitted.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ public @NonNull Builder setPressure(@FloatRange(from = 0f) float pressure) {
+ if (pressure < 0f) {
+ throw new IllegalArgumentException("Touch event pressure cannot be negative");
+ }
+ mPressure = pressure;
+ return this;
+ }
+
+ /**
+ * Sets the major axis size of the event. This field is optional and can be omitted.
+ *
+ * @return this builder, to allow for chaining of calls
+ */
+ public @NonNull Builder setMajorAxisSize(@FloatRange(from = 0f) float majorAxisSize) {
+ if (majorAxisSize < 0f) {
+ throw new IllegalArgumentException(
+ "Touch event major axis size cannot be negative");
+ }
+ mMajorAxisSize = majorAxisSize;
+ return this;
+ }
+ }
+
+ public static final @NonNull Parcelable.Creator<VirtualTouchEvent> CREATOR =
+ new Parcelable.Creator<VirtualTouchEvent>() {
+ public VirtualTouchEvent createFromParcel(Parcel source) {
+ return new VirtualTouchEvent(source);
+ }
+ public VirtualTouchEvent[] newArray(int size) {
+ return new VirtualTouchEvent[size];
+ }
+ };
+}
diff --git a/core/java/android/hardware/input/VirtualTouchscreen.java b/core/java/android/hardware/input/VirtualTouchscreen.java
new file mode 100644
index 000000000000..c8d602acaff6
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualTouchscreen.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.companion.virtual.IVirtualDevice;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.io.Closeable;
+
+/**
+ * A virtual touchscreen representing a touch-based display input mechanism on a remote device.
+ *
+ * This registers an InputDevice that is interpreted like a physically-connected device and
+ * dispatches received events to it.
+ *
+ * @hide
+ */
+@SystemApi
+public class VirtualTouchscreen implements Closeable {
+
+ private final IVirtualDevice mVirtualDevice;
+ private final IBinder mToken;
+
+ /** @hide */
+ public VirtualTouchscreen(IVirtualDevice virtualDevice, IBinder token) {
+ mVirtualDevice = virtualDevice;
+ mToken = token;
+ }
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void close() {
+ try {
+ mVirtualDevice.unregisterInputDevice(mToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sends a touch event to the system.
+ *
+ * @param event the event to send
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void sendTouchEvent(@NonNull VirtualTouchEvent event) {
+ try {
+ mVirtualDevice.sendTouchEvent(mToken, event);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index b069fb336d55..e2d7847b85e9 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -22,9 +22,11 @@ import android.annotation.SystemApi;
import android.app.AppOpsManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.util.ExceptionUtils;
+import android.util.IntArray;
import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BinderCallHeavyHitterWatcher;
import com.android.internal.os.BinderCallHeavyHitterWatcher.BinderCallHeavyHitterListener;
import com.android.internal.os.BinderInternal;
@@ -140,33 +142,55 @@ public class Binder implements IBinder {
/**
* Flag indicating whether we should be tracing transact calls.
*/
- private static volatile boolean sTracingEnabled = false;
+ private static volatile boolean sStackTrackingEnabled = false;
+
+ private static final Object sTracingUidsWriteLock = new Object();
+ private static volatile IntArray sTracingUidsImmutable = new IntArray();
/**
- * Enable Binder IPC tracing.
+ * Enable Binder IPC stack tracking. If enabled, every binder transaction will be logged to
+ * {@link TransactionTracker}.
*
* @hide
*/
- public static void enableTracing() {
- sTracingEnabled = true;
+ public static void enableStackTracking() {
+ sStackTrackingEnabled = true;
}
/**
- * Disable Binder IPC tracing.
+ * Disable Binder IPC stack tracking.
*
* @hide
*/
- public static void disableTracing() {
- sTracingEnabled = false;
+ public static void disableStackTracking() {
+ sStackTrackingEnabled = false;
+ }
+
+ /**
+ * @hide
+ */
+ public static void enableTracingForUid(int uid) {
+ synchronized (sTracingUidsWriteLock) {
+ final IntArray copy = sTracingUidsImmutable.clone();
+ copy.add(uid);
+ sTracingUidsImmutable = copy;
+ }
}
/**
- * Check if binder transaction tracing is enabled.
+ * Check if binder transaction stack tracking is enabled.
*
* @hide
*/
- public static boolean isTracingEnabled() {
- return sTracingEnabled;
+ public static boolean isStackTrackingEnabled() {
+ return sStackTrackingEnabled;
+ }
+
+ /**
+ * @hide
+ */
+ public static boolean isTracingEnabled(int callingUid) {
+ return sTracingUidsImmutable.indexOf(callingUid) != -1;
}
/**
@@ -288,6 +312,9 @@ public class Binder implements IBinder {
private IInterface mOwner;
private String mDescriptor;
+ private volatile String[] mTransactionTraceNames = null;
+ private volatile String mSimpleDescriptor = null;
+ private static final int TRANSACTION_TRACE_NAME_ID_LIMIT = 1024;
/**
* Return the ID of the process that sent you the current transaction
@@ -884,6 +911,53 @@ public class Binder implements IBinder {
}
/**
+ * @hide
+ */
+ @VisibleForTesting
+ public final @NonNull String getTransactionTraceName(int transactionCode) {
+ if (mTransactionTraceNames == null) {
+ final String descriptor = getSimpleDescriptor();
+ final int highestId = Math.min(getMaxTransactionId(), TRANSACTION_TRACE_NAME_ID_LIMIT);
+ final String[] transactionNames = new String[highestId + 1];
+ final StringBuffer buf = new StringBuffer();
+ for (int i = 0; i <= highestId; i++) {
+ String transactionName = getTransactionName(i + FIRST_CALL_TRANSACTION);
+ if (transactionName != null) {
+ buf.append(descriptor).append(':').append(transactionName);
+ } else {
+ buf.append(descriptor).append('#').append(i + FIRST_CALL_TRANSACTION);
+ }
+ transactionNames[i] = buf.toString();
+ buf.setLength(0);
+ }
+ mTransactionTraceNames = transactionNames;
+ mSimpleDescriptor = descriptor;
+ }
+ final int index = transactionCode - FIRST_CALL_TRANSACTION;
+ if (index < 0 || index >= mTransactionTraceNames.length) {
+ return mSimpleDescriptor + "#" + transactionCode;
+ }
+ return mTransactionTraceNames[index];
+ }
+
+ private String getSimpleDescriptor() {
+ final int dot = mDescriptor.lastIndexOf(".");
+ if (dot > 0) {
+ // Strip the package name
+ return mDescriptor.substring(dot + 1);
+ }
+ return mDescriptor;
+ }
+
+ /**
+ * @return The highest user-defined transaction id of all transactions.
+ * @hide
+ */
+ public int getMaxTransactionId() {
+ return 0;
+ }
+
+ /**
* Implemented to call the more convenient version
* {@link #dump(FileDescriptor, PrintWriter, String[])}.
*/
@@ -1181,7 +1255,8 @@ public class Binder implements IBinder {
// Log any exceptions as warnings, don't silently suppress them.
// If the call was {@link IBinder#FLAG_ONEWAY} then these exceptions
// disappear into the ether.
- final boolean tracingEnabled = Binder.isTracingEnabled();
+ final boolean tracingEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_AIDL) &&
+ (Binder.isStackTrackingEnabled() || Binder.isTracingEnabled(callingUid));
try {
final BinderCallHeavyHitterWatcher heavyHitterWatcher = sHeavyHitterWatcher;
if (heavyHitterWatcher != null) {
@@ -1189,9 +1264,7 @@ public class Binder implements IBinder {
heavyHitterWatcher.onTransaction(callingUid, getClass(), code);
}
if (tracingEnabled) {
- final String transactionName = getTransactionName(code);
- Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, getClass().getName() + ":"
- + (transactionName != null ? transactionName : code));
+ Trace.traceBegin(Trace.TRACE_TAG_AIDL, getTransactionTraceName(code));
}
if ((flags & FLAG_COLLECT_NOTED_APP_OPS) != 0) {
@@ -1226,7 +1299,7 @@ public class Binder implements IBinder {
res = true;
} finally {
if (tracingEnabled) {
- Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
+ Trace.traceEnd(Trace.TRACE_TAG_AIDL);
}
if (observer != null) {
// The parcel RPC headers have been called during onTransact so we can now access
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 483b549c5637..e9299207ea6b 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -545,7 +545,7 @@ public final class BinderProxy implements IBinder {
}
}
- final boolean tracingEnabled = Binder.isTracingEnabled();
+ final boolean tracingEnabled = Binder.isStackTrackingEnabled();
if (tracingEnabled) {
final Throwable tr = new Throwable();
Binder.getTransactionTracker().addTrace(tr);
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 8292f26a8c6b..aa4b83a5c361 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -192,14 +192,15 @@ public class GraphicsEnvironment {
// We only want to use ANGLE if the developer has explicitly chosen something other than
// default driver.
- final boolean requested = devOptIn.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE);
- if (requested) {
+ final boolean forceAngle = devOptIn.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE);
+ final boolean forceNative = devOptIn.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE);
+ if (forceAngle || forceNative) {
Log.v(TAG, "ANGLE developer option for " + packageName + ": " + devOptIn);
}
final boolean gameModeEnabledAngle = isAngleEnabledByGameMode(context, packageName);
- return requested || gameModeEnabledAngle;
+ return !forceNative && (forceAngle || gameModeEnabledAngle);
}
private int getVulkanVersion(PackageManager pm) {
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 83a6bc0ec99d..73ffd66486d2 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -44,7 +44,6 @@ import android.content.res.TypedArray;
import android.graphics.BLASTBufferQueue;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.GraphicBuffer;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.Point;
@@ -1930,9 +1929,7 @@ public abstract class WallpaperService extends Service {
mScreenshotSize.set(mSurfaceSize.x, mSurfaceSize.y);
- GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(hardwareBuffer);
-
- t.setBuffer(mScreenshotSurfaceControl, graphicBuffer);
+ t.setBuffer(mScreenshotSurfaceControl, hardwareBuffer);
t.setColorSpace(mScreenshotSurfaceControl, screenshotBuffer.getColorSpace());
// Place on top everything else.
t.setLayer(mScreenshotSurfaceControl, Integer.MAX_VALUE);
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 6984e4dfccc4..4789231b0404 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -874,6 +874,18 @@ public class StaticLayout extends Layout {
? Math.max(fmDescent, Math.round(descents[breakIndex]))
: fmDescent;
+ // The fallback ascent/descent may be larger than top/bottom of the default font
+ // metrics. Adjust top/bottom with ascent/descent for avoiding unexpected
+ // clipping.
+ if (fallbackLineSpacing) {
+ if (ascent < fmTop) {
+ fmTop = ascent;
+ }
+ if (descent > fmBottom) {
+ fmBottom = descent;
+ }
+ }
+
v = out(source, here, endPos,
ascent, descent, fmTop, fmBottom,
v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java
index b77265b0ebf6..7b28b8a607de 100644
--- a/core/java/android/util/IntArray.java
+++ b/core/java/android/util/IntArray.java
@@ -34,7 +34,7 @@ public class IntArray implements Cloneable {
private int[] mValues;
private int mSize;
- private IntArray(int[] array, int size) {
+ private IntArray(int[] array, int size) {
mValues = array;
mSize = Preconditions.checkArgumentInRange(size, 0, array.length, "size");
}
@@ -178,10 +178,8 @@ public class IntArray implements Cloneable {
}
@Override
- public IntArray clone() throws CloneNotSupportedException {
- final IntArray clone = (IntArray) super.clone();
- clone.mValues = mValues.clone();
- return clone;
+ public IntArray clone() {
+ return new IntArray(mValues.clone(), mSize);
}
/**
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index b8e50fc6adf2..adb8b86493d5 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1495,6 +1495,15 @@ public final class MotionEvent extends InputEvent implements Parcelable {
*/
public static final int TOOL_TYPE_ERASER = 4;
+ /**
+ * Tool type constant: The tool is a palm and should be rejected.
+ *
+ * @see #getToolType
+ *
+ * @hide
+ */
+ public static final int TOOL_TYPE_PALM = 5;
+
// NOTE: If you add a new tool type here you must also add it to:
// native/include/android/input.h
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index af7d86cdd1d9..3b5270960c99 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -120,6 +120,8 @@ public final class SurfaceControl implements Parcelable {
long relativeToObject, int zorder);
private static native void nativeSetPosition(long transactionObj, long nativeObject,
float x, float y);
+ private static native void nativeSetScale(long transactionObj, long nativeObject,
+ float x, float y);
private static native void nativeSetSize(long transactionObj, long nativeObject, int w, int h);
private static native void nativeSetTransparentRegionHint(long transactionObj,
long nativeObject, Region region);
@@ -202,9 +204,13 @@ public final class SurfaceControl implements Parcelable {
private static native void nativeReparent(long transactionObj, long nativeObject,
long newParentNativeObject);
private static native void nativeSetBuffer(long transactionObj, long nativeObject,
- GraphicBuffer buffer);
+ HardwareBuffer buffer);
+ private static native void nativeSetBufferTransform(long transactionObj, long nativeObject,
+ int transform);
private static native void nativeSetColorSpace(long transactionObj, long nativeObject,
int colorSpace);
+ private static native void nativeSetDamageRegion(long transactionObj, long nativeObject,
+ Region region);
private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
@@ -1333,7 +1339,6 @@ public final class SurfaceControl implements Parcelable {
* Set the initial visibility for the SurfaceControl.
*
* @param hidden Whether the Surface is initially HIDDEN.
- * @hide
*/
@NonNull
public Builder setHidden(boolean hidden) {
@@ -2840,16 +2845,38 @@ public final class SurfaceControl implements Parcelable {
}
/**
- * @hide
+ * Sets the SurfaceControl to the specified position relative to the parent
+ * SurfaceControl
+ *
+ * @param sc The SurfaceControl to change position
+ * @param x the X position
+ * @param y the Y position
+ * @return this transaction
*/
- @UnsupportedAppUsage
- public Transaction setPosition(SurfaceControl sc, float x, float y) {
+ @NonNull
+ public Transaction setPosition(@NonNull SurfaceControl sc, float x, float y) {
checkPreconditions(sc);
nativeSetPosition(mNativeObject, sc.mNativeObject, x, y);
return this;
}
/**
+ * Sets the SurfaceControl to the specified scale with (0, 0) as the center point
+ * of the scale.
+ *
+ * @param sc The SurfaceControl to change scale
+ * @param scaleX the X scale
+ * @param scaleY the Y scale
+ * @return this transaction
+ */
+ @NonNull
+ public Transaction setScale(@NonNull SurfaceControl sc, float scaleX, float scaleY) {
+ checkPreconditions(sc);
+ nativeSetScale(mNativeObject, sc.mNativeObject, scaleX, scaleY);
+ return this;
+ }
+
+ /**
* Set the default buffer size for the SurfaceControl, if there is a
* {@link Surface} associated with the control, then
* this will be the default size for buffers dequeued from it.
@@ -3056,7 +3083,9 @@ public final class SurfaceControl implements Parcelable {
* @param sc SurfaceControl to set crop of.
* @param crop Bounds of the crop to apply.
* @hide
+ * @deprecated Use {@link #setCrop(SurfaceControl, Rect)} instead.
*/
+ @Deprecated
@UnsupportedAppUsage
public Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
checkPreconditions(sc);
@@ -3071,6 +3100,28 @@ public final class SurfaceControl implements Parcelable {
}
/**
+ * Bounds the surface and its children to the bounds specified. Size of the surface will be
+ * ignored and only the crop and buffer size will be used to determine the bounds of the
+ * surface. If no crop is specified and the surface has no buffer, the surface bounds is
+ * only constrained by the size of its parent bounds.
+ *
+ * @param sc SurfaceControl to set crop of.
+ * @param crop Bounds of the crop to apply.
+ * @return this This transaction for chaining
+ */
+ public @NonNull Transaction setCrop(@NonNull SurfaceControl sc, @Nullable Rect crop) {
+ checkPreconditions(sc);
+ if (crop != null) {
+ nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
+ crop.left, crop.top, crop.right, crop.bottom);
+ } else {
+ nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, 0, 0);
+ }
+
+ return this;
+ }
+
+ /**
* Same as {@link Transaction#setWindowCrop(SurfaceControl, Rect)} but sets the crop rect
* top left at 0, 0.
*
@@ -3215,11 +3266,34 @@ public final class SurfaceControl implements Parcelable {
}
/**
- * Sets the opacity of the surface. Setting the flag is equivalent to creating the
- * Surface with the {@link #OPAQUE} flag.
- * @hide
+ * Indicates whether the surface must be considered opaque, even if its pixel format is
+ * set to translucent. This can be useful if an application needs full RGBA 8888 support
+ * for instance but will still draw every pixel opaque.
+ * <p>
+ * This flag only determines whether opacity will be sampled from the alpha channel.
+ * Plane-alpha from calls to setAlpha() can still result in blended composition
+ * regardless of the opaque setting.
+ *
+ * Combined effects are (assuming a buffer format with an alpha channel):
+ * <ul>
+ * <li>OPAQUE + alpha(1.0) == opaque composition
+ * <li>OPAQUE + alpha(0.x) == blended composition
+ * <li>OPAQUE + alpha(0.0) == no composition
+ * <li>!OPAQUE + alpha(1.0) == blended composition
+ * <li>!OPAQUE + alpha(0.x) == blended composition
+ * <li>!OPAQUE + alpha(0.0) == no composition
+ * </ul>
+ * If the underlying buffer lacks an alpha channel, it is as if setOpaque(true)
+ * were set automatically.
+ *
+ * @see Builder#setOpaque(boolean)
+ *
+ * @param sc The SurfaceControl to update
+ * @param isOpaque true if the buffer's alpha should be ignored, false otherwise
+ * @return this
*/
- public Transaction setOpaque(SurfaceControl sc, boolean isOpaque) {
+ @NonNull
+ public Transaction setOpaque(@NonNull SurfaceControl sc, boolean isOpaque) {
checkPreconditions(sc);
if (isOpaque) {
nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
@@ -3498,14 +3572,57 @@ public final class SurfaceControl implements Parcelable {
* created as type {@link #FX_SURFACE_BLAST}
*
* @hide
+ * @deprecated Use {@link #setBuffer(SurfaceControl, HardwareBuffer)} instead
*/
+ @Deprecated
public Transaction setBuffer(SurfaceControl sc, GraphicBuffer buffer) {
+ return setBuffer(sc, HardwareBuffer.createFromGraphicBuffer(buffer));
+ }
+
+ /**
+ * Updates the HardwareBuffer displayed for the SurfaceControl.
+ *
+ * Note that the buffer must be allocated with {@link HardwareBuffer#USAGE_COMPOSER_OVERLAY}
+ * as well as {@link HardwareBuffer#USAGE_GPU_SAMPLED_IMAGE} as the surface control might
+ * be composited using either an overlay or using the GPU.
+ *
+ * @param sc The SurfaceControl to update
+ * @param buffer The buffer to be displayed
+ * @return this
+ */
+ public @NonNull Transaction setBuffer(@NonNull SurfaceControl sc,
+ @Nullable HardwareBuffer buffer) {
checkPreconditions(sc);
nativeSetBuffer(mNativeObject, sc.mNativeObject, buffer);
return this;
}
/**
+ * Sets the buffer transform that should be applied to the current buffer.
+ *
+ * @param sc The SurfaceControl to update
+ * @param transform The transform to apply to the buffer.
+ * @return this
+ */
+ public @NonNull Transaction setBufferTransform(@NonNull SurfaceControl sc,
+ /* TODO: Mark the intdef */ int transform) {
+ checkPreconditions(sc);
+ nativeSetBufferTransform(mNativeObject, sc.mNativeObject, transform);
+ return this;
+ }
+
+ /**
+ * Updates the region for the content on this surface updated in this transaction.
+ *
+ * If unspecified, the complete surface is assumed to be damaged.
+ */
+ public @NonNull Transaction setDamageRegion(@NonNull SurfaceControl sc,
+ @Nullable Region region) {
+ nativeSetDamageRegion(mNativeObject, sc.mNativeObject, region);
+ return this;
+ }
+
+ /**
* Set the color space for the SurfaceControl. The supported color spaces are SRGB
* and Display P3, other color spaces will be treated as SRGB. This can only be used for
* SurfaceControls that were created as type {@link #FX_SURFACE_BLAST}
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 3b15db2ded70..b85fe7c3aae2 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -836,20 +836,25 @@ public interface InputConnection {
boolean beginBatchEdit();
/**
- * Tell the editor that you are done with a batch edit previously
- * initiated with {@link #beginBatchEdit}. This ends the latest
- * batch only.
- *
- * <p><strong>IME authors:</strong> make sure you call this
- * exactly once for each call to {@link #beginBatchEdit}.</p>
- *
- * <p><strong>Editor authors:</strong> please be careful about
- * batch edit nesting. Updates still to be held back until the end
- * of the last batch edit.</p>
+ * Tell the editor that you are done with a batch edit previously initiated with
+ * {@link #beginBatchEdit()}. This ends the latest batch only.
+ *
+ * <p><strong>IME authors:</strong> make sure you call this exactly once for each call to
+ * {@link #beginBatchEdit()}.</p>
+ *
+ * <p><strong>Editor authors:</strong> please be careful about batch edit nesting. Updates still
+ * to be held back until the end of the last batch edit. In case you are delegating this API
+ * call to the one obtained from
+ * {@link android.widget.EditText#onCreateInputConnection(EditorInfo)}, there was an off-by-one
+ * that had returned {@code true} when its nested batch edit count becomes {@code 0} as a result
+ * of invoking this API. This bug is fixed in {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+ * </p>
*
- * @return true if there is still a batch edit in progress after closing
- * the latest one (in other words, if the nesting count is > 0), false
- * otherwise or if the input connection is no longer valid.
+ * @return For editor authors, you must return {@code true} if a batch edit is still in progress
+ * after closing the latest one (in other words, if the nesting count is still a
+ * positive number). Return {@code false} otherwise. For IME authors, you will
+ * always receive {@code true} as long as the request was sent to the editor, and
+ * receive {@code false} only if the input connection is no longer valid.
*/
boolean endBatchEdit();
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index 8532763f9dad..2702c2d69c2b 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -437,7 +437,10 @@ public class UiTranslationController {
if (view.getViewTranslationResponse() != null
&& view.getViewTranslationResponse().equals(response)) {
if (callback instanceof TextViewTranslationCallback) {
- if (((TextViewTranslationCallback) callback).isShowingTranslation()) {
+ TextViewTranslationCallback textViewCallback =
+ (TextViewTranslationCallback) callback;
+ if (textViewCallback.isShowingTranslation()
+ || textViewCallback.isAnimationRunning()) {
if (DEBUG) {
Log.d(TAG, "Duplicate ViewTranslationResponse for " + autofillId
+ ". Ignoring.");
diff --git a/core/java/android/widget/TextViewTranslationCallback.java b/core/java/android/widget/TextViewTranslationCallback.java
index 4a78f3ee6fac..942be21b1ade 100644
--- a/core/java/android/widget/TextViewTranslationCallback.java
+++ b/core/java/android/widget/TextViewTranslationCallback.java
@@ -46,6 +46,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
private TranslationTransformationMethod mTranslationTransformation;
private boolean mIsShowingTranslation = false;
+ private boolean mAnimationRunning = false;
private boolean mIsTextPaddingEnabled = false;
private CharSequence mPaddedText;
private int mAnimationDurationMillis = 250; // default value
@@ -92,6 +93,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
(TextView) view,
() -> {
mIsShowingTranslation = true;
+ mAnimationRunning = false;
// TODO(b/178353965): well-handle setTransformationMethod.
((TextView) view).setTransformationMethod(transformation);
});
@@ -124,6 +126,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
(TextView) view,
() -> {
mIsShowingTranslation = false;
+ mAnimationRunning = false;
((TextView) view).setTransformationMethod(transformation);
});
if (!TextUtils.isEmpty(mContentDescription)) {
@@ -162,6 +165,13 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
return mIsShowingTranslation;
}
+ /**
+ * Returns whether the view is running animation to show or hide the translation.
+ */
+ public boolean isAnimationRunning() {
+ return mAnimationRunning;
+ }
+
@Override
public void enableContentPadding() {
mIsTextPaddingEnabled = true;
@@ -230,6 +240,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
mAnimator.end();
// Note: mAnimator is now null; do not use again here.
}
+ mAnimationRunning = true;
int fadedOutColor = colorWithAlpha(view.getCurrentTextColor(), 0);
mAnimator = ValueAnimator.ofArgb(view.getCurrentTextColor(), fadedOutColor);
mAnimator.addUpdateListener(
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index b331a9e81e27..4ba7ef26e9cb 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -15,7 +15,6 @@
*/
package android.window;
-import static android.window.ConfigurationHelper.diffPublicWithSizeBuckets;
import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
import static android.window.ConfigurationHelper.isDifferentDisplay;
import static android.window.ConfigurationHelper.shouldUpdateResources;
@@ -222,14 +221,7 @@ public class WindowTokenClient extends IWindowToken.Stub {
() -> windowContext.dispatchConfigurationChanged(newConfig));
}
- // Dispatch onConfigurationChanged only if there's a significant public change to
- // make it compatible with the original behavior.
- final Configuration[] sizeConfigurations = context.getResources()
- .getSizeConfigurations();
- final SizeConfigurationBuckets buckets = sizeConfigurations != null
- ? new SizeConfigurationBuckets(sizeConfigurations) : null;
- final int diff = diffPublicWithSizeBuckets(mConfiguration, newConfig, buckets);
-
+ final int diff = mConfiguration.diffPublicOnly(newConfig);
if (shouldReportConfigChange && diff != 0
&& context instanceof WindowProviderService) {
final WindowProviderService windowProviderService = (WindowProviderService) context;
diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
index 410e68bf3581..29bb3111d83e 100644
--- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java
+++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
@@ -89,7 +89,7 @@ public final class EditableInputConnection extends BaseInputConnection
// contribution to mTextView's nested batch edit count is zero.
mTextView.endBatchEdit();
mBatchEditNesting--;
- return true;
+ return mBatchEditNesting > 0;
}
}
return false;
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index fb5fded923f2..67d0c52960e0 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -597,6 +597,14 @@ static void nativeSetPosition(JNIEnv* env, jclass clazz, jlong transactionObj,
transaction->setPosition(ctrl, x, y);
}
+static void nativeSetScale(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
+ jfloat xScale, jfloat yScale) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+ transaction->setMatrix(ctrl, xScale, 0, 0, yScale);
+}
+
static void nativeSetGeometry(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
jobject sourceObj, jobject dstObj, jlong orientation) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -620,9 +628,19 @@ static void nativeSetBuffer(JNIEnv* env, jclass clazz, jlong transactionObj, jlo
jobject bufferObject) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
- sp<GraphicBuffer> buffer(
- android_graphics_GraphicBuffer_getNativeGraphicsBuffer(env, bufferObject));
- transaction->setBuffer(ctrl, buffer);
+ sp<GraphicBuffer> graphicBuffer(GraphicBuffer::fromAHardwareBuffer(
+ android_hardware_HardwareBuffer_getNativeHardwareBuffer(env, bufferObject)));
+ transaction->setBuffer(ctrl, graphicBuffer);
+}
+
+static void nativeSetBufferTransform(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jlong nativeObject, jint transform) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+ transaction->setTransform(ctrl, transform);
+ bool transformToInverseDisplay = (NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY & transform) ==
+ NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
+ transaction->setTransformToDisplayInverse(ctrl, transformToInverseDisplay);
}
static void nativeSetColorSpace(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
@@ -740,6 +758,37 @@ static void nativeSetTransparentRegionHint(JNIEnv* env, jclass clazz, jlong tran
}
}
+static void nativeSetDamageRegion(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jlong nativeObject, jobject regionObj) {
+ SurfaceControl* const surfaceControl = reinterpret_cast<SurfaceControl*>(nativeObject);
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+ if (regionObj == nullptr) {
+ transaction->setSurfaceDamageRegion(surfaceControl, Region::INVALID_REGION);
+ return;
+ }
+
+ graphics::RegionIterator iterator(env, regionObj);
+ if (!iterator.isValid()) {
+ transaction->setSurfaceDamageRegion(surfaceControl, Region::INVALID_REGION);
+ return;
+ }
+
+ Region region;
+ while (!iterator.isDone()) {
+ ARect rect = iterator.getRect();
+ region.orSelf(static_cast<const Rect&>(rect));
+ iterator.next();
+ }
+
+ if (region.getBounds().isEmpty()) {
+ transaction->setSurfaceDamageRegion(surfaceControl, Region::INVALID_REGION);
+ return;
+ }
+
+ transaction->setSurfaceDamageRegion(surfaceControl, region);
+}
+
static void nativeSetAlpha(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jfloat alpha) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -1905,10 +1954,14 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
(void*)nativeSetRelativeLayer },
{"nativeSetPosition", "(JJFF)V",
(void*)nativeSetPosition },
+ {"nativeSetScale", "(JJFF)V",
+ (void*)nativeSetScale },
{"nativeSetSize", "(JJII)V",
(void*)nativeSetSize },
{"nativeSetTransparentRegionHint", "(JJLandroid/graphics/Region;)V",
(void*)nativeSetTransparentRegionHint },
+ { "nativeSetDamageRegion", "(JJLandroid/graphics/Region;)V",
+ (void*)nativeSetDamageRegion },
{"nativeSetAlpha", "(JJF)V",
(void*)nativeSetAlpha },
{"nativeSetColor", "(JJ[F)V",
@@ -2018,8 +2071,9 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
(void*)nativeGetDisplayedContentSample },
{"nativeSetGeometry", "(JJLandroid/graphics/Rect;Landroid/graphics/Rect;J)V",
(void*)nativeSetGeometry },
- {"nativeSetBuffer", "(JJLandroid/graphics/GraphicBuffer;)V",
+ {"nativeSetBuffer", "(JJLandroid/hardware/HardwareBuffer;)V",
(void*)nativeSetBuffer },
+ {"nativeSetBufferTransform", "(JJI)V", (void*) nativeSetBufferTransform},
{"nativeSetColorSpace", "(JJI)V",
(void*)nativeSetColorSpace },
{"nativeSyncInputWindows", "(J)V",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4c367326b670..08948773f599 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2066,7 +2066,7 @@
<permission android:name="android.permission.BLUETOOTH_PRIVILEGED"
android:protectionLevel="signature|privileged" />
- <!-- Control access to email providers exclusively for Bluetooth
+ <!-- @SystemApi Control access to email providers exclusively for Bluetooth
@hide
-->
<permission android:name="android.permission.BLUETOOTH_MAP"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5b7d1b9607d5..ddec469f63ad 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2383,7 +2383,7 @@
<!-- If supported and enabled, are dreams activated when asleep and charging? (by default) -->
<bool name="config_dreamsActivatedOnSleepByDefault">false</bool>
<!-- ComponentName of the default dream (Settings.Secure.DEFAULT_SCREENSAVER_COMPONENT) -->
- <string name="config_dreamsDefaultComponent" translatable="false">com.google.android.deskclock/com.android.deskclock.Screensaver</string>
+ <string name="config_dreamsDefaultComponent" translatable="false">com.android.deskclock/com.android.deskclock.Screensaver</string>
<!-- Are we allowed to dream while not plugged in? -->
<bool name="config_dreamsEnabledOnBattery">false</bool>
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 64b303b724dc..32d72b37b8af 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -23,6 +23,7 @@ android_test {
],
aidl: {
+ generate_get_transaction_name: true,
local_include_dirs: ["aidl"],
},
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualKeyEventTest.java b/core/tests/coretests/src/android/hardware/input/VirtualKeyEventTest.java
new file mode 100644
index 000000000000..37cc9b70dd1b
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/input/VirtualKeyEventTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.testng.Assert.assertThrows;
+
+import android.view.KeyEvent;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualKeyEventTest {
+
+ @Test
+ public void keyEvent_emptyBuilder() {
+ assertThrows(IllegalArgumentException.class, () -> new VirtualKeyEvent.Builder().build());
+ }
+
+ @Test
+ public void keyEvent_noKeyCode() {
+ assertThrows(IllegalArgumentException.class,
+ () -> new VirtualKeyEvent.Builder().setAction(VirtualKeyEvent.ACTION_DOWN).build());
+ }
+
+ @Test
+ public void keyEvent_noAction() {
+ assertThrows(IllegalArgumentException.class,
+ () -> new VirtualKeyEvent.Builder().setKeyCode(KeyEvent.KEYCODE_A).build());
+ }
+
+ @Test
+ public void keyEvent_created() {
+ final VirtualKeyEvent event = new VirtualKeyEvent.Builder()
+ .setAction(VirtualKeyEvent.ACTION_DOWN)
+ .setKeyCode(KeyEvent.KEYCODE_A).build();
+ assertWithMessage("Incorrect key code").that(event.getKeyCode()).isEqualTo(
+ KeyEvent.KEYCODE_A);
+ assertWithMessage("Incorrect action").that(event.getAction()).isEqualTo(
+ VirtualKeyEvent.ACTION_DOWN);
+ }
+}
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualMouseButtonEventTest.java b/core/tests/coretests/src/android/hardware/input/VirtualMouseButtonEventTest.java
new file mode 100644
index 000000000000..789e0bb2ff56
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/input/VirtualMouseButtonEventTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.testng.Assert.assertThrows;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualMouseButtonEventTest {
+
+ @Test
+ public void buttonEvent_emptyBuilder() {
+ assertThrows(IllegalArgumentException.class,
+ () -> new VirtualMouseButtonEvent.Builder().build());
+ }
+
+ @Test
+ public void buttonEvent_noButtonCode() {
+ assertThrows(IllegalArgumentException.class, () -> new VirtualMouseButtonEvent.Builder()
+ .setAction(VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE).build());
+ }
+
+ @Test
+ public void buttonEvent_noAction() {
+ assertThrows(IllegalArgumentException.class, () -> new VirtualMouseButtonEvent.Builder()
+ .setButtonCode(VirtualMouseButtonEvent.BUTTON_BACK).build());
+ }
+
+ @Test
+ public void buttonEvent_created() {
+ final VirtualMouseButtonEvent event = new VirtualMouseButtonEvent.Builder()
+ .setAction(VirtualMouseButtonEvent.ACTION_BUTTON_PRESS)
+ .setButtonCode(VirtualMouseButtonEvent.BUTTON_BACK).build();
+ assertWithMessage("Incorrect button code").that(event.getButtonCode()).isEqualTo(
+ VirtualMouseButtonEvent.BUTTON_BACK);
+ assertWithMessage("Incorrect action").that(event.getAction()).isEqualTo(
+ VirtualMouseButtonEvent.ACTION_BUTTON_PRESS);
+ }
+}
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualMouseRelativeEventTest.java b/core/tests/coretests/src/android/hardware/input/VirtualMouseRelativeEventTest.java
new file mode 100644
index 000000000000..c0508162869b
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/input/VirtualMouseRelativeEventTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualMouseRelativeEventTest {
+
+ @Test
+ public void relativeEvent_created() {
+ final VirtualMouseRelativeEvent event = new VirtualMouseRelativeEvent.Builder()
+ .setRelativeX(-5f)
+ .setRelativeY(8f).build();
+ assertWithMessage("Incorrect x value").that(event.getRelativeX()).isEqualTo(-5f);
+ assertWithMessage("Incorrect y value").that(event.getRelativeY()).isEqualTo(8f);
+ }
+}
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualMouseScrollEventTest.java b/core/tests/coretests/src/android/hardware/input/VirtualMouseScrollEventTest.java
new file mode 100644
index 000000000000..2259c740da7e
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/input/VirtualMouseScrollEventTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.testng.Assert.assertThrows;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualMouseScrollEventTest {
+
+ @Test
+ public void scrollEvent_xOutOfRange() {
+ assertThrows(IllegalArgumentException.class, () -> new VirtualMouseScrollEvent.Builder()
+ .setXAxisMovement(1.5f)
+ .setYAxisMovement(1.0f));
+ }
+
+ @Test
+ public void scrollEvent_yOutOfRange() {
+ assertThrows(IllegalArgumentException.class, () -> new VirtualMouseScrollEvent.Builder()
+ .setXAxisMovement(0.5f)
+ .setYAxisMovement(1.1f));
+ }
+
+ @Test
+ public void scrollEvent_created() {
+ final VirtualMouseScrollEvent event = new VirtualMouseScrollEvent.Builder()
+ .setXAxisMovement(-1f)
+ .setYAxisMovement(1f).build();
+ assertWithMessage("Incorrect x value").that(event.getXAxisMovement()).isEqualTo(-1f);
+ assertWithMessage("Incorrect y value").that(event.getYAxisMovement()).isEqualTo(1f);
+ }
+}
+
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualTouchEventTest.java b/core/tests/coretests/src/android/hardware/input/VirtualTouchEventTest.java
new file mode 100644
index 000000000000..3f504a00773c
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/input/VirtualTouchEventTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.testng.Assert.assertThrows;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualTouchEventTest {
+
+ @Test
+ public void touchEvent_emptyBuilder() {
+ assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder().build());
+ }
+
+ @Test
+ public void touchEvent_noAction() {
+ assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder()
+ .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER)
+ .setX(0f)
+ .setY(1f)
+ .setPointerId(1)
+ .build());
+ }
+
+ @Test
+ public void touchEvent_noPointerId() {
+ assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder()
+ .setAction(VirtualTouchEvent.ACTION_DOWN)
+ .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER)
+ .setX(0f)
+ .setY(1f)
+ .build());
+ }
+
+ @Test
+ public void touchEvent_noToolType() {
+ assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder()
+ .setAction(VirtualTouchEvent.ACTION_DOWN)
+ .setX(0f)
+ .setY(1f)
+ .setPointerId(1)
+ .build());
+ }
+
+ @Test
+ public void touchEvent_noX() {
+ assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder()
+ .setAction(VirtualTouchEvent.ACTION_DOWN)
+ .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER)
+ .setY(1f)
+ .setPointerId(1)
+ .build());
+ }
+
+
+ @Test
+ public void touchEvent_noY() {
+ assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder()
+ .setAction(VirtualTouchEvent.ACTION_DOWN)
+ .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER)
+ .setX(0f)
+ .setPointerId(1)
+ .build());
+ }
+
+ @Test
+ public void touchEvent_created() {
+ final VirtualTouchEvent event = new VirtualTouchEvent.Builder()
+ .setAction(VirtualTouchEvent.ACTION_DOWN)
+ .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER)
+ .setX(0f)
+ .setY(1f)
+ .setPointerId(1)
+ .build();
+ assertWithMessage("Incorrect action").that(event.getAction()).isEqualTo(
+ VirtualTouchEvent.ACTION_DOWN);
+ assertWithMessage("Incorrect tool type").that(event.getToolType()).isEqualTo(
+ VirtualTouchEvent.TOOL_TYPE_FINGER);
+ assertWithMessage("Incorrect x").that(event.getX()).isEqualTo(0f);
+ assertWithMessage("Incorrect y").that(event.getY()).isEqualTo(1f);
+ assertWithMessage("Incorrect pointer id").that(event.getPointerId()).isEqualTo(1);
+ }
+
+ @Test
+ public void touchEvent_created_withPressureAndAxis() {
+ final VirtualTouchEvent event = new VirtualTouchEvent.Builder()
+ .setAction(VirtualTouchEvent.ACTION_DOWN)
+ .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER)
+ .setX(0f)
+ .setY(1f)
+ .setPointerId(1)
+ .setPressure(0.5f)
+ .setMajorAxisSize(10f)
+ .build();
+ assertWithMessage("Incorrect action").that(event.getAction()).isEqualTo(
+ VirtualTouchEvent.ACTION_DOWN);
+ assertWithMessage("Incorrect tool type").that(event.getToolType()).isEqualTo(
+ VirtualTouchEvent.TOOL_TYPE_FINGER);
+ assertWithMessage("Incorrect x").that(event.getX()).isEqualTo(0f);
+ assertWithMessage("Incorrect y").that(event.getY()).isEqualTo(1f);
+ assertWithMessage("Incorrect pointer id").that(event.getPointerId()).isEqualTo(1);
+ assertWithMessage("Incorrect pressure").that(event.getPressure()).isEqualTo(0.5f);
+ assertWithMessage("Incorrect major axis size").that(event.getMajorAxisSize()).isEqualTo(
+ 10f);
+ }
+
+ @Test
+ public void touchEvent_cancelUsedImproperly() {
+ assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder()
+ .setAction(VirtualTouchEvent.ACTION_CANCEL)
+ .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER)
+ .setX(0f)
+ .setY(1f)
+ .setPointerId(1)
+ .build());
+ }
+
+ @Test
+ public void touchEvent_palmUsedImproperly() {
+ assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder()
+ .setAction(VirtualTouchEvent.ACTION_MOVE)
+ .setToolType(VirtualTouchEvent.TOOL_TYPE_PALM)
+ .setX(0f)
+ .setY(1f)
+ .setPointerId(1)
+ .build());
+ }
+
+ @Test
+ public void touchEvent_palmAndCancelUsedProperly() {
+ final VirtualTouchEvent event = new VirtualTouchEvent.Builder()
+ .setAction(VirtualTouchEvent.ACTION_CANCEL)
+ .setToolType(VirtualTouchEvent.TOOL_TYPE_PALM)
+ .setX(0f)
+ .setY(1f)
+ .setPointerId(1)
+ .setPressure(0.5f)
+ .setMajorAxisSize(10f)
+ .build();
+ assertWithMessage("Incorrect action").that(event.getAction()).isEqualTo(
+ VirtualTouchEvent.ACTION_CANCEL);
+ assertWithMessage("Incorrect tool type").that(event.getToolType()).isEqualTo(
+ VirtualTouchEvent.TOOL_TYPE_PALM);
+ assertWithMessage("Incorrect x").that(event.getX()).isEqualTo(0f);
+ assertWithMessage("Incorrect y").that(event.getY()).isEqualTo(1f);
+ assertWithMessage("Incorrect pointer id").that(event.getPointerId()).isEqualTo(1);
+ assertWithMessage("Incorrect pressure").that(event.getPressure()).isEqualTo(0.5f);
+ assertWithMessage("Incorrect major axis size").that(event.getMajorAxisSize()).isEqualTo(
+ 10f);
+ }
+}
diff --git a/core/tests/coretests/src/android/os/AidlTest.java b/core/tests/coretests/src/android/os/AidlTest.java
index 4c5141537c6a..5f54b093e5e5 100644
--- a/core/tests/coretests/src/android/os/AidlTest.java
+++ b/core/tests/coretests/src/android/os/AidlTest.java
@@ -27,11 +27,12 @@ import java.util.List;
public class AidlTest extends TestCase {
private IAidlTest mRemote;
+ private AidlObject mLocal;
@Override
protected void setUp() throws Exception {
super.setUp();
- AidlObject mLocal = new AidlObject();
+ mLocal = new AidlObject();
mRemote = IAidlTest.Stub.asInterface(mLocal);
}
@@ -417,5 +418,24 @@ public class AidlTest extends TestCase {
}
assertEquals(good, true);
}
+
+ @SmallTest
+ public void testGetTransactionName() throws Exception {
+ assertEquals(15, mLocal.getMaxTransactionId());
+
+ assertEquals("booleanArray",
+ mLocal.getTransactionName(IAidlTest.Stub.TRANSACTION_booleanArray));
+ assertEquals("voidSecurityException",
+ mLocal.getTransactionName(IAidlTest.Stub.TRANSACTION_voidSecurityException));
+ assertEquals("parcelableIn",
+ mLocal.getTransactionName(IAidlTest.Stub.TRANSACTION_parcelableIn));
+
+ assertEquals("IAidlTest:booleanArray",
+ mLocal.getTransactionTraceName(IAidlTest.Stub.TRANSACTION_booleanArray));
+ assertEquals("IAidlTest:voidSecurityException",
+ mLocal.getTransactionTraceName(IAidlTest.Stub.TRANSACTION_voidSecurityException));
+ assertEquals("IAidlTest:parcelableIn",
+ mLocal.getTransactionTraceName(IAidlTest.Stub.TRANSACTION_parcelableIn));
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index af19bd0a79ac..b8e8b0114b47 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -295,11 +295,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity,
@NonNull TaskFragmentContainer secondaryContainer,
@NonNull SplitRule splitRule) {
+ SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity,
+ secondaryContainer, splitRule);
+ // Remove container later to prevent pinning escaping toast showing in lock task mode.
if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) {
removeExistingSecondaryContainers(wct, primaryContainer);
}
- SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity,
- secondaryContainer, splitRule);
mSplitContainers.add(splitContainer);
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 81be21cbd7aa..ade573132eef 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -112,8 +112,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
secondaryContainer.setLastRequestedBounds(secondaryRectBounds);
// Set adjacent to each other so that the containers below will be invisible.
- setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
- secondaryContainer.getTaskFragmentToken(), rule);
+ setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule);
mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
@@ -149,8 +148,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
secondaryActivity, secondaryRectBounds, primaryContainer);
// Set adjacent to each other so that the containers below will be invisible.
- setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
- secondaryContainer.getTaskFragmentToken(), rule);
+ setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule);
mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
@@ -269,8 +267,22 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds);
- setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
- secondaryContainer.getTaskFragmentToken(), rule);
+ setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule);
+ }
+
+ private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer primaryContainer,
+ @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule) {
+ final Rect parentBounds = getParentContainerBounds(primaryContainer);
+ // Clear adjacent TaskFragments if the container is shown in fullscreen, or the
+ // secondaryContainer could not be finished.
+ if (!shouldShowSideBySide(parentBounds, splitRule)) {
+ setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
+ null /* secondary */, null /* splitRule */);
+ } else {
+ setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
+ secondaryContainer.getTaskFragmentToken(), splitRule);
+ }
}
/**
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml b/libs/WindowManager/Shell/res/drawable/compat_hint_bubble.xml
index 22cd384e1be0..26848b13a1bc 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml
+++ b/libs/WindowManager/Shell/res/drawable/compat_hint_bubble.xml
@@ -16,6 +16,6 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
- <solid android:color="@color/size_compat_background"/>
- <corners android:radius="@dimen/size_compat_hint_corner_radius"/>
+ <solid android:color="@color/compat_controls_background"/>
+ <corners android:radius="@dimen/compat_hint_corner_radius"/>
</shape> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml b/libs/WindowManager/Shell/res/drawable/compat_hint_point.xml
index af9063a94afb..0e0ca37aaf25 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml
+++ b/libs/WindowManager/Shell/res/drawable/compat_hint_point.xml
@@ -15,11 +15,11 @@
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="@dimen/size_compat_hint_point_width"
+ android:width="@dimen/compat_hint_point_width"
android:height="8dp"
android:viewportWidth="10"
android:viewportHeight="8">
<path
- android:fillColor="@color/size_compat_background"
+ android:fillColor="@color/compat_controls_background"
android:pathData="M10,0 l-4.1875,6.6875 a1,1 0 0,1 -1.625,0 l-4.1875,-6.6875z"/>
</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
index 18caa3582537..ab74e43472c3 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
@@ -20,16 +20,16 @@
android:viewportWidth="48"
android:viewportHeight="48">
<path
- android:fillColor="@color/size_compat_background"
+ android:fillColor="@color/compat_controls_background"
android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0" />
<group
android:translateX="12"
android:translateY="12">
<path
- android:fillColor="@color/size_compat_text"
+ android:fillColor="@color/compat_controls_text"
android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02C8.17,18.43 6,15.97 6,13z"/>
<path
- android:fillColor="@color/size_compat_text"
+ android:fillColor="@color/compat_controls_text"
android:pathData="M20,13c0,-4.42 -3.58,-8 -8,-8c-0.06,0 -0.12,0.01 -0.18,0.01v0l1.09,-1.09L11.5,2.5L8,6l3.5,3.5l1.41,-1.41l-1.08,-1.08C11.89,7.01 11.95,7 12,7c3.31,0 6,2.69 6,6c0,2.97 -2.17,5.43 -5,5.91v2.02C16.95,20.44 20,17.08 20,13z"/>
</group>
</vector>
diff --git a/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml
new file mode 100644
index 000000000000..c04e258ea784
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:clipToPadding="false"
+ android:paddingEnd="@dimen/compat_hint_padding_end"
+ android:paddingBottom="5dp"
+ android:clickable="true">
+
+ <TextView
+ android:id="@+id/compat_mode_hint_text"
+ android:layout_width="188dp"
+ android:layout_height="wrap_content"
+ android:lineSpacingExtra="4sp"
+ android:background="@drawable/compat_hint_bubble"
+ android:padding="16dp"
+ android:textAlignment="viewStart"
+ android:textColor="@color/compat_controls_text"
+ android:textSize="14sp"/>
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end"
+ android:src="@drawable/compat_hint_point"
+ android:paddingHorizontal="@dimen/compat_hint_corner_radius"
+ android:contentDescription="@null"/>
+
+</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/layout/size_compat_ui.xml b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
index 82ebee263a64..6f946b25eaa5 100644
--- a/libs/WindowManager/Shell/res/layout/size_compat_ui.xml
+++ b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
@@ -14,10 +14,15 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.wm.shell.sizecompatui.SizeCompatRestartButton
+<com.android.wm.shell.compatui.CompatUILayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="bottom|end">
+
+ <include android:id="@+id/size_compat_hint"
+ layout="@layout/compat_mode_hint"/>
<FrameLayout
android:layout_width="@dimen/size_compat_button_width"
@@ -36,4 +41,4 @@
</FrameLayout>
-</com.android.wm.shell.sizecompatui.SizeCompatRestartButton>
+</com.android.wm.shell.compatui.CompatUILayout>
diff --git a/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml
deleted file mode 100644
index d0e7c42dbf8b..000000000000
--- a/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.wm.shell.sizecompatui.SizeCompatHintPopup
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
-
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:clipToPadding="false"
- android:paddingBottom="5dp">
-
- <LinearLayout
- android:id="@+id/size_compat_hint_popup"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:clickable="true">
-
- <TextView
- android:layout_width="188dp"
- android:layout_height="wrap_content"
- android:lineSpacingExtra="4sp"
- android:background="@drawable/size_compat_hint_bubble"
- android:padding="16dp"
- android:text="@string/restart_button_description"
- android:textAlignment="viewStart"
- android:textColor="@color/size_compat_text"
- android:textSize="14sp"/>
-
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="end"
- android:src="@drawable/size_compat_hint_point"
- android:paddingHorizontal="@dimen/size_compat_hint_corner_radius"
- android:contentDescription="@null"/>
-
- </LinearLayout>
-
- </FrameLayout>
-
-</com.android.wm.shell.sizecompatui.SizeCompatHintPopup>
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index 23a21724e43d..cf596f7d15dc 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -30,9 +30,9 @@
<color name="bubbles_dark">@color/GM2_grey_800</color>
<color name="bubbles_icon_tint">@color/GM2_grey_700</color>
- <!-- Size Compat Restart Button -->
- <color name="size_compat_background">@android:color/system_neutral1_800</color>
- <color name="size_compat_text">@android:color/system_neutral1_50</color>
+ <!-- Compat controls UI -->
+ <color name="compat_controls_background">@android:color/system_neutral1_800</color>
+ <color name="compat_controls_text">@android:color/system_neutral1_50</color>
<!-- GM2 colors -->
<color name="GM2_grey_200">#E8EAED</color>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 9e77578eafd8..18e91f41a698 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -206,11 +206,15 @@
<!-- The height of the size compat restart button including padding. -->
<dimen name="size_compat_button_height">64dp</dimen>
- <!-- The radius of the corners of the size compat hint bubble. -->
- <dimen name="size_compat_hint_corner_radius">28dp</dimen>
+ <!-- The radius of the corners of the compat hint bubble. -->
+ <dimen name="compat_hint_corner_radius">28dp</dimen>
- <!-- The width of the size compat hint point. -->
- <dimen name="size_compat_hint_point_width">10dp</dimen>
+ <!-- The width of the compat hint point. -->
+ <dimen name="compat_hint_point_width">10dp</dimen>
+
+ <!-- The end padding for the compat hint. Computed as (size_compat_button_width / 2
+ - compat_hint_corner_radius - compat_hint_point_width /2). -->
+ <dimen name="compat_hint_padding_end">7dp</dimen>
<!-- The width of the brand image on staring surface. -->
<dimen name="starting_surface_brand_image_width">200dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 8e98b82088dc..8b3a35688f11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -52,8 +52,8 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.sizecompatui.SizeCompatUIController;
import com.android.wm.shell.startingsurface.StartingWindowController;
import java.io.PrintWriter;
@@ -69,7 +69,7 @@ import java.util.function.Consumer;
* TODO(b/167582004): may consider consolidating this class and TaskOrganizer
*/
public class ShellTaskOrganizer extends TaskOrganizer implements
- SizeCompatUIController.SizeCompatUICallback {
+ CompatUIController.CompatUICallback {
// Intentionally using negative numbers here so the positive numbers can be used
// for task id specific listeners that will be added later.
@@ -98,9 +98,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
default void onTaskInfoChanged(RunningTaskInfo taskInfo) {}
default void onTaskVanished(RunningTaskInfo taskInfo) {}
default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {}
- /** Whether this task listener supports size compat UI. */
- default boolean supportSizeCompatUI() {
- // All TaskListeners should support size compat except PIP.
+ /** Whether this task listener supports compat UI. */
+ default boolean supportCompatUI() {
+ // All TaskListeners should support compat UI except PIP.
return true;
}
/** Attaches the a child window surface to the task surface. */
@@ -159,11 +159,11 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
private StartingWindowController mStartingWindow;
/**
- * In charge of showing size compat UI. Can be {@code null} if device doesn't support size
+ * In charge of showing compat UI. Can be {@code null} if device doesn't support size
* compat.
*/
@Nullable
- private final SizeCompatUIController mSizeCompatUI;
+ private final CompatUIController mCompatUI;
@Nullable
private final Optional<RecentTasksController> mRecentTasks;
@@ -172,32 +172,32 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
private RunningTaskInfo mLastFocusedTaskInfo;
public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
- this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */,
+ this(null /* taskOrganizerController */, mainExecutor, context, null /* compatUI */,
Optional.empty() /* recentTasksController */);
}
public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
- SizeCompatUIController sizeCompatUI) {
- this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI,
+ CompatUIController compatUI) {
+ this(null /* taskOrganizerController */, mainExecutor, context, compatUI,
Optional.empty() /* recentTasksController */);
}
public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
- SizeCompatUIController sizeCompatUI,
+ CompatUIController compatUI,
Optional<RecentTasksController> recentTasks) {
- this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI,
+ this(null /* taskOrganizerController */, mainExecutor, context, compatUI,
recentTasks);
}
@VisibleForTesting
ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor,
- Context context, @Nullable SizeCompatUIController sizeCompatUI,
+ Context context, @Nullable CompatUIController compatUI,
Optional<RecentTasksController> recentTasks) {
super(taskOrganizerController, mainExecutor);
- mSizeCompatUI = sizeCompatUI;
+ mCompatUI = compatUI;
mRecentTasks = recentTasks;
- if (sizeCompatUI != null) {
- sizeCompatUI.setSizeCompatUICallback(this);
+ if (compatUI != null) {
+ compatUI.setCompatUICallback(this);
}
}
@@ -428,7 +428,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
listener.onTaskAppeared(info.getTaskInfo(), info.getLeash());
}
notifyLocusVisibilityIfNeeded(info.getTaskInfo());
- notifySizeCompatUI(info.getTaskInfo(), listener);
+ notifyCompatUI(info.getTaskInfo(), listener);
}
/**
@@ -459,8 +459,8 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
notifyLocusVisibilityIfNeeded(taskInfo);
if (updated || !taskInfo.equalsForSizeCompat(data.getTaskInfo())) {
- // Notify the size compat UI if the listener or task info changed.
- notifySizeCompatUI(taskInfo, newListener);
+ // Notify the compat UI if the listener or task info changed.
+ notifyCompatUI(taskInfo, newListener);
}
if (data.getTaskInfo().getWindowingMode() != taskInfo.getWindowingMode()) {
// Notify the recent tasks when a task changes windowing modes
@@ -504,8 +504,8 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
listener.onTaskVanished(taskInfo);
}
notifyLocusVisibilityIfNeeded(taskInfo);
- // Pass null for listener to remove the size compat UI on this task if there is any.
- notifySizeCompatUI(taskInfo, null /* taskListener */);
+ // Pass null for listener to remove the compat UI on this task if there is any.
+ notifyCompatUI(taskInfo, null /* taskListener */);
// Notify the recent tasks that a task has been removed
mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskRemoved(taskInfo));
}
@@ -618,28 +618,28 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
/**
- * Notifies {@link SizeCompatUIController} about the size compat info changed on the give Task
+ * Notifies {@link CompatUIController} about the compat info changed on the give Task
* to update the UI accordingly.
*
* @param taskInfo the new Task info
* @param taskListener listener to handle the Task Surface placement. {@code null} if task is
* vanished.
*/
- private void notifySizeCompatUI(RunningTaskInfo taskInfo, @Nullable TaskListener taskListener) {
- if (mSizeCompatUI == null) {
+ private void notifyCompatUI(RunningTaskInfo taskInfo, @Nullable TaskListener taskListener) {
+ if (mCompatUI == null) {
return;
}
- // The task is vanished or doesn't support size compat UI, notify to remove size compat UI
+ // The task is vanished or doesn't support compat UI, notify to remove compat UI
// on this Task if there is any.
- if (taskListener == null || !taskListener.supportSizeCompatUI()
+ if (taskListener == null || !taskListener.supportCompatUI()
|| !taskInfo.topActivityInSizeCompat || !taskInfo.isVisible) {
- mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
+ mCompatUI.onCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
null /* taskConfig */, null /* taskListener */);
return;
}
- mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
+ mCompatUI.onCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
taskInfo.configuration, taskListener);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
index d92e2ccc77bd..686fbbfd6f7c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
@@ -15,23 +15,22 @@
*/
package com.android.wm.shell.bubbles;
-import static android.graphics.Paint.ANTI_ALIAS_FLAG;
-import static android.graphics.Paint.DITHER_FLAG;
-import static android.graphics.Paint.FILTER_BITMAP_FLAG;
-
+import android.annotation.DrawableRes;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Outline;
-import android.graphics.Paint;
-import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Path;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.PathParser;
+import android.view.Gravity;
import android.view.View;
import android.view.ViewOutlineProvider;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.launcher3.icons.DotRenderer;
@@ -47,7 +46,7 @@ import java.util.EnumSet;
* Badge = the icon associated with the app that created this bubble, this will show work profile
* badge if appropriate.
*/
-public class BadgedImageView extends ImageView {
+public class BadgedImageView extends FrameLayout {
/** Same value as Launcher3 dot code */
public static final float WHITE_SCRIM_ALPHA = 0.54f;
@@ -74,6 +73,9 @@ public class BadgedImageView extends ImageView {
private final EnumSet<SuppressionFlag> mDotSuppressionFlags =
EnumSet.of(SuppressionFlag.FLYOUT_VISIBLE);
+ private final ImageView mBubbleIcon;
+ private final ImageView mAppIcon;
+
private float mDotScale = 0f;
private float mAnimatingToDotScale = 0f;
private boolean mDotIsAnimating = false;
@@ -86,7 +88,6 @@ public class BadgedImageView extends ImageView {
private DotRenderer.DrawParams mDrawParams;
private int mDotColor;
- private Paint mPaint = new Paint(ANTI_ALIAS_FLAG);
private Rect mTempBounds = new Rect();
public BadgedImageView(Context context) {
@@ -104,6 +105,17 @@ public class BadgedImageView extends ImageView {
public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+
+ mBubbleIcon = new ImageView(context);
+ addView(mBubbleIcon);
+ mAppIcon = new ImageView(context);
+ addView(mAppIcon);
+
+ final TypedArray ta = mContext.obtainStyledAttributes(attrs, new int[]{android.R.attr.src},
+ defStyleAttr, defStyleRes);
+ mBubbleIcon.setImageResource(ta.getResourceId(0, 0));
+ ta.recycle();
+
mDrawParams = new DotRenderer.DrawParams();
setFocusable(true);
@@ -135,7 +147,6 @@ public class BadgedImageView extends ImageView {
public void showDotAndBadge(boolean onLeft) {
removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.BEHIND_STACK);
animateDotBadgePositions(onLeft);
-
}
public void hideDotAndBadge(boolean onLeft) {
@@ -149,6 +160,7 @@ public class BadgedImageView extends ImageView {
*/
public void setRenderedBubble(BubbleViewProvider bubble) {
mBubble = bubble;
+ mBubbleIcon.setImageBitmap(bubble.getBubbleIcon());
if (mDotSuppressionFlags.contains(SuppressionFlag.BEHIND_STACK)) {
hideBadge();
} else {
@@ -176,6 +188,20 @@ public class BadgedImageView extends ImageView {
mDotRenderer.draw(canvas, mDrawParams);
}
+ /**
+ * Set drawable resource shown as the icon
+ */
+ public void setIconImageResource(@DrawableRes int drawable) {
+ mBubbleIcon.setImageResource(drawable);
+ }
+
+ /**
+ * Get icon drawable
+ */
+ public Drawable getIconDrawable() {
+ return mBubbleIcon.getDrawable();
+ }
+
/** Adds a dot suppression flag, updating dot visibility if needed. */
void addDotSuppressionFlag(SuppressionFlag flag) {
if (mDotSuppressionFlags.add(flag)) {
@@ -279,7 +305,6 @@ public class BadgedImageView extends ImageView {
showBadge();
}
-
/** Whether to draw the dot in onDraw(). */
private boolean shouldDrawDot() {
// Always render the dot if it's animating, since it could be animating out. Otherwise, show
@@ -325,29 +350,28 @@ public class BadgedImageView extends ImageView {
void showBadge() {
Bitmap badge = mBubble.getAppBadge();
if (badge == null) {
- setImageBitmap(mBubble.getBubbleIcon());
+ mAppIcon.setVisibility(GONE);
return;
}
- Canvas bubbleCanvas = new Canvas();
- Bitmap noBadgeBubble = mBubble.getBubbleIcon();
- Bitmap bubble = noBadgeBubble.copy(noBadgeBubble.getConfig(), /* isMutable */ true);
- bubbleCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
- bubbleCanvas.setBitmap(bubble);
- final int bubbleSize = bubble.getWidth();
+ final int bubbleSize = mBubble.getBubbleIcon().getWidth();
final int badgeSize = (int) (ICON_BADGE_SCALE * bubbleSize);
- Rect dest = new Rect();
+
+ FrameLayout.LayoutParams appIconParams = (LayoutParams) mAppIcon.getLayoutParams();
+ appIconParams.height = badgeSize;
+ appIconParams.width = badgeSize;
if (mOnLeft) {
- dest.set(0, bubbleSize - badgeSize, badgeSize, bubbleSize);
+ appIconParams.gravity = Gravity.BOTTOM | Gravity.LEFT;
} else {
- dest.set(bubbleSize - badgeSize, bubbleSize - badgeSize, bubbleSize, bubbleSize);
+ appIconParams.gravity = Gravity.BOTTOM | Gravity.RIGHT;
}
- bubbleCanvas.drawBitmap(badge, null /* src */, dest, mPaint);
- bubbleCanvas.setBitmap(null);
- setImageBitmap(bubble);
+ mAppIcon.setLayoutParams(appIconParams);
+
+ mAppIcon.setImageBitmap(badge);
+ mAppIcon.setVisibility(VISIBLE);
}
void hideBadge() {
- setImageBitmap(mBubble.getBubbleIcon());
+ mAppIcon.setVisibility(GONE);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 519a856538c7..cd635c10fd8e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -328,6 +328,7 @@ public class BubbleData {
if (prevBubble == null) {
// Create a new bubble
bubble.setSuppressFlyout(suppressFlyout);
+ bubble.markUpdatedAt(mTimeSource.currentTimeMillis());
doAdd(bubble);
trim();
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 5161092fb96c..a175929cf498 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -66,7 +66,7 @@ class BubbleOverflow(
updateResources()
getExpandedView()?.applyThemeAttrs()
// Apply inset and new style to fresh icon drawable.
- getIconView()?.setImageResource(R.drawable.bubble_ic_overflow_button)
+ getIconView()?.setIconImageResource(R.drawable.bubble_ic_overflow_button)
updateBtnTheme()
}
@@ -89,19 +89,19 @@ class BubbleOverflow(
dotColor = colorAccent
val shapeColor = res.getColor(android.R.color.system_accent1_1000)
- overflowBtn?.drawable?.setTint(shapeColor)
+ overflowBtn?.iconDrawable?.setTint(shapeColor)
val iconFactory = BubbleIconFactory(context)
// Update bitmap
- val fg = InsetDrawable(overflowBtn?.drawable, overflowIconInset)
+ val fg = InsetDrawable(overflowBtn?.iconDrawable, overflowIconInset)
bitmap = iconFactory.createBadgedIconBitmap(
AdaptiveIconDrawable(ColorDrawable(colorAccent), fg)).icon
// Update dot path
dotPath = PathParser.createPathFromPathData(
res.getString(com.android.internal.R.string.config_icon_mask))
- val scale = iconFactory.normalizer.getScale(iconView!!.drawable,
+ val scale = iconFactory.normalizer.getScale(iconView!!.iconDrawable,
null /* outBounds */, null /* path */, null /* outMaskShape */)
val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f
val matrix = Matrix()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
index eea6e3cb35db..c4bd73ba1b4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.common;
-import android.graphics.GraphicBuffer;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.view.SurfaceControl;
@@ -63,8 +62,6 @@ public class ScreenshotUtils {
if (buffer == null || buffer.getHardwareBuffer() == null) {
return;
}
- final GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
- buffer.getHardwareBuffer());
mScreenshot = new SurfaceControl.Builder()
.setName("ScreenshotUtils screenshot")
.setFormat(PixelFormat.TRANSLUCENT)
@@ -73,7 +70,7 @@ public class ScreenshotUtils {
.setBLASTLayer()
.build();
- mTransaction.setBuffer(mScreenshot, graphicBuffer);
+ mTransaction.setBuffer(mScreenshot, buffer.getHardwareBuffer());
mTransaction.setColorSpace(mScreenshot, buffer.getColorSpace());
mTransaction.reparent(mScreenshot, mSurfaceControl);
mTransaction.setLayer(mScreenshot, mLayer);
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 040fffae3e84..b8ac87fa5d4c 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
@@ -93,7 +93,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private final InsetsState mInsetsState = new InsetsState();
private Context mContext;
- private DividerSnapAlgorithm mDividerSnapAlgorithm;
+ @VisibleForTesting DividerSnapAlgorithm mDividerSnapAlgorithm;
private WindowContainerToken mWinToken1;
private WindowContainerToken mWinToken2;
private int mDividePosition;
@@ -294,20 +294,22 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mSplitLayoutHandler.onLayoutSizeChanging(this);
}
- void setDividePosition(int position) {
+ void setDividePosition(int position, boolean applyLayoutChange) {
mDividePosition = position;
updateBounds(mDividePosition);
- mSplitLayoutHandler.onLayoutSizeChanged(this);
+ if (applyLayoutChange) {
+ mSplitLayoutHandler.onLayoutSizeChanged(this);
+ }
}
- /** Sets divide position base on the ratio within root bounds. */
+ /** Updates divide position and split bounds base on the ratio within root bounds. */
public void setDivideRatio(float ratio) {
final int position = isLandscape()
? mRootBounds.left + (int) (mRootBounds.width() * ratio)
: mRootBounds.top + (int) (mRootBounds.height() * ratio);
- DividerSnapAlgorithm.SnapTarget snapTarget =
+ final DividerSnapAlgorithm.SnapTarget snapTarget =
mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(position);
- setDividePosition(snapTarget.position);
+ setDividePosition(snapTarget.position, false /* applyLayoutChange */);
}
/** Resets divider position. */
@@ -336,7 +338,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
break;
default:
flingDividePosition(currentPosition, snapTarget.position,
- () -> setDividePosition(snapTarget.position));
+ () -> setDividePosition(snapTarget.position, true /* applyLayoutChange */));
break;
}
}
@@ -389,7 +391,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
@Override
public void onAnimationCancel(Animator animation) {
- setDividePosition(to);
+ setDividePosition(to, true /* applyLayoutChange */);
}
});
animator.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java
index a703114194a0..99dbfe01964c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java
@@ -14,17 +14,17 @@
* limitations under the License.
*/
-package com.android.wm.shell.sizecompatui;
+package com.android.wm.shell.compatui;
import com.android.wm.shell.common.annotations.ExternalThread;
/**
- * Interface to engage size compat UI.
+ * Interface to engage compat UI.
*/
@ExternalThread
-public interface SizeCompatUI {
+public interface CompatUI {
/**
- * Called when the keyguard occluded state changes. Removes all size compat UIs if the
+ * Called when the keyguard occluded state changes. Removes all compat UIs if the
* keyguard is now occluded.
* @param occluded indicates if the keyguard is now occluded.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index e06070ab12e5..e0b23873a980 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.sizecompatui;
+package com.android.wm.shell.compatui;
import android.annotation.Nullable;
import android.content.Context;
@@ -48,20 +48,20 @@ import java.util.function.Predicate;
/**
* Controls to show/update restart-activity buttons on Tasks based on whether the foreground
- * activities are in size compatibility mode.
+ * activities are in compatibility mode.
*/
-public class SizeCompatUIController implements OnDisplaysChangedListener,
+public class CompatUIController implements OnDisplaysChangedListener,
DisplayImeController.ImePositionProcessor {
/** Callback for size compat UI interaction. */
- public interface SizeCompatUICallback {
+ public interface CompatUICallback {
/** Called when the size compat restart button appears. */
void onSizeCompatRestartButtonAppeared(int taskId);
/** Called when the size compat restart button is clicked. */
void onSizeCompatRestartButtonClicked(int taskId);
}
- private static final String TAG = "SizeCompatUIController";
+ private static final String TAG = "CompatUIController";
/** Whether the IME is shown on display id. */
private final Set<Integer> mDisplaysWithIme = new ArraySet<>(1);
@@ -71,7 +71,7 @@ public class SizeCompatUIController implements OnDisplaysChangedListener,
new SparseArray<>(0);
/** The showing UIs by task id. */
- private final SparseArray<SizeCompatUILayout> mActiveLayouts = new SparseArray<>(0);
+ private final SparseArray<CompatUIWindowManager> mActiveLayouts = new SparseArray<>(0);
/** Avoid creating display context frequently for non-default display. */
private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
@@ -82,17 +82,17 @@ public class SizeCompatUIController implements OnDisplaysChangedListener,
private final DisplayImeController mImeController;
private final SyncTransactionQueue mSyncQueue;
private final ShellExecutor mMainExecutor;
- private final SizeCompatUIImpl mImpl = new SizeCompatUIImpl();
+ private final CompatUIImpl mImpl = new CompatUIImpl();
- private SizeCompatUICallback mCallback;
+ private CompatUICallback mCallback;
/** Only show once automatically in the process life. */
private boolean mHasShownHint;
- /** Indicates if the keyguard is currently occluded, in which case size compat UIs shouldn't
+ /** Indicates if the keyguard is currently occluded, in which case compat UIs shouldn't
* be shown. */
private boolean mKeyguardOccluded;
- public SizeCompatUIController(Context context,
+ public CompatUIController(Context context,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
DisplayImeController imeController,
@@ -108,35 +108,36 @@ public class SizeCompatUIController implements OnDisplaysChangedListener,
mImeController.addPositionProcessor(this);
}
- public SizeCompatUI asSizeCompatUI() {
+ /** Returns implementation of {@link CompatUI}. */
+ public CompatUI asCompatUI() {
return mImpl;
}
/** Sets the callback for UI interactions. */
- public void setSizeCompatUICallback(SizeCompatUICallback callback) {
+ public void setCompatUICallback(CompatUICallback callback) {
mCallback = callback;
}
/**
- * Called when the Task info changed. Creates and updates the size compat UI if there is an
+ * Called when the Task info changed. Creates and updates the compat UI if there is an
* activity in size compat, or removes the UI if there is no size compat activity.
*
* @param displayId display the task and activity are in.
* @param taskId task the activity is in.
- * @param taskConfig task config to place the size compat UI with.
+ * @param taskConfig task config to place the compat UI with.
* @param taskListener listener to handle the Task Surface placement.
*/
- public void onSizeCompatInfoChanged(int displayId, int taskId,
+ public void onCompatInfoChanged(int displayId, int taskId,
@Nullable Configuration taskConfig,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
if (taskConfig == null || taskListener == null) {
- // Null token means the current foreground activity is not in size compatibility mode.
+ // Null token means the current foreground activity is not in compatibility mode.
removeLayout(taskId);
} else if (mActiveLayouts.contains(taskId)) {
// UI already exists, update the UI layout.
updateLayout(taskId, taskConfig, taskListener);
} else {
- // Create a new size compat UI.
+ // Create a new compat UI.
createLayout(displayId, taskId, taskConfig, taskListener);
}
}
@@ -151,7 +152,7 @@ public class SizeCompatUIController implements OnDisplaysChangedListener,
mDisplayContextCache.remove(displayId);
removeOnInsetsChangedListener(displayId);
- // Remove all size compat UIs on the removed display.
+ // Remove all compat UIs on the removed display.
final List<Integer> toRemoveTaskIds = new ArrayList<>();
forAllLayoutsOnDisplay(displayId, layout -> toRemoveTaskIds.add(layout.getTaskId()));
for (int i = toRemoveTaskIds.size() - 1; i >= 0; i--) {
@@ -194,7 +195,7 @@ public class SizeCompatUIController implements OnDisplaysChangedListener,
mDisplaysWithIme.remove(displayId);
}
- // Hide the size compat UIs when input method is showing.
+ // Hide the compat UIs when input method is showing.
forAllLayoutsOnDisplay(displayId,
layout -> layout.updateVisibility(showOnDisplay(displayId)));
}
@@ -202,7 +203,7 @@ public class SizeCompatUIController implements OnDisplaysChangedListener,
@VisibleForTesting
void onKeyguardOccludedChanged(boolean occluded) {
mKeyguardOccluded = occluded;
- // Hide the size compat UIs when keyguard is occluded.
+ // Hide the compat UIs when keyguard is occluded.
forAllLayouts(layout -> layout.updateVisibility(showOnDisplay(layout.getDisplayId())));
}
@@ -222,34 +223,34 @@ public class SizeCompatUIController implements OnDisplaysChangedListener,
return;
}
- final SizeCompatUILayout layout = createLayout(context, displayId, taskId, taskConfig,
- taskListener);
- mActiveLayouts.put(taskId, layout);
- layout.createSizeCompatButton(showOnDisplay(displayId));
+ final CompatUIWindowManager compatUIWindowManager =
+ createLayout(context, displayId, taskId, taskConfig, taskListener);
+ mActiveLayouts.put(taskId, compatUIWindowManager);
+ compatUIWindowManager.createLayout(showOnDisplay(displayId));
}
@VisibleForTesting
- SizeCompatUILayout createLayout(Context context, int displayId, int taskId,
+ CompatUIWindowManager createLayout(Context context, int displayId, int taskId,
Configuration taskConfig, ShellTaskOrganizer.TaskListener taskListener) {
- final SizeCompatUILayout layout = new SizeCompatUILayout(mSyncQueue, mCallback, context,
- taskConfig, taskId, taskListener, mDisplayController.getDisplayLayout(displayId),
- mHasShownHint);
+ final CompatUIWindowManager compatUIWindowManager = new CompatUIWindowManager(context,
+ taskConfig, mSyncQueue, mCallback, taskId, taskListener,
+ mDisplayController.getDisplayLayout(displayId), mHasShownHint);
// Only show hint for the first time.
mHasShownHint = true;
- return layout;
+ return compatUIWindowManager;
}
private void updateLayout(int taskId, Configuration taskConfig,
ShellTaskOrganizer.TaskListener taskListener) {
- final SizeCompatUILayout layout = mActiveLayouts.get(taskId);
+ final CompatUIWindowManager layout = mActiveLayouts.get(taskId);
if (layout == null) {
return;
}
- layout.updateSizeCompatInfo(taskConfig, taskListener, showOnDisplay(layout.getDisplayId()));
+ layout.updateCompatInfo(taskConfig, taskListener, showOnDisplay(layout.getDisplayId()));
}
private void removeLayout(int taskId) {
- final SizeCompatUILayout layout = mActiveLayouts.get(taskId);
+ final CompatUIWindowManager layout = mActiveLayouts.get(taskId);
if (layout != null) {
layout.release();
mActiveLayouts.remove(taskId);
@@ -275,19 +276,19 @@ public class SizeCompatUIController implements OnDisplaysChangedListener,
return context;
}
- private void forAllLayoutsOnDisplay(int displayId, Consumer<SizeCompatUILayout> callback) {
+ private void forAllLayoutsOnDisplay(int displayId, Consumer<CompatUIWindowManager> callback) {
forAllLayouts(layout -> layout.getDisplayId() == displayId, callback);
}
- private void forAllLayouts(Consumer<SizeCompatUILayout> callback) {
+ private void forAllLayouts(Consumer<CompatUIWindowManager> callback) {
forAllLayouts(layout -> true, callback);
}
- private void forAllLayouts(Predicate<SizeCompatUILayout> condition,
- Consumer<SizeCompatUILayout> callback) {
+ private void forAllLayouts(Predicate<CompatUIWindowManager> condition,
+ Consumer<CompatUIWindowManager> callback) {
for (int i = 0; i < mActiveLayouts.size(); i++) {
final int taskId = mActiveLayouts.keyAt(i);
- final SizeCompatUILayout layout = mActiveLayouts.get(taskId);
+ final CompatUIWindowManager layout = mActiveLayouts.get(taskId);
if (layout != null && condition.test(layout)) {
callback.accept(layout);
}
@@ -298,11 +299,11 @@ public class SizeCompatUIController implements OnDisplaysChangedListener,
* The interface for calls from outside the Shell, within the host process.
*/
@ExternalThread
- private class SizeCompatUIImpl implements SizeCompatUI {
+ private class CompatUIImpl implements CompatUI {
@Override
public void onKeyguardOccludedChanged(boolean occluded) {
mMainExecutor.execute(() -> {
- SizeCompatUIController.this.onKeyguardOccludedChanged(occluded);
+ CompatUIController.this.onKeyguardOccludedChanged(occluded);
});
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
new file mode 100644
index 000000000000..ea4f20968438
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Container for compat UI controls.
+ */
+public class CompatUILayout extends LinearLayout {
+
+ private CompatUIWindowManager mWindowManager;
+
+ public CompatUILayout(Context context) {
+ this(context, null);
+ }
+
+ public CompatUILayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CompatUILayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public CompatUILayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ void inject(CompatUIWindowManager windowManager) {
+ mWindowManager = windowManager;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ // Need to relayout after changes like hiding / showing a hint since they affect size.
+ // Doing this directly in setSizeCompatHintVisibility can result in flaky animation.
+ mWindowManager.relayout();
+ }
+
+ void setSizeCompatHintVisibility(boolean show) {
+ final LinearLayout sizeCompatHint = findViewById(R.id.size_compat_hint);
+ int visibility = show ? View.VISIBLE : View.GONE;
+ if (sizeCompatHint.getVisibility() == visibility) {
+ return;
+ }
+ sizeCompatHint.setVisibility(visibility);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ final ImageButton restartButton = findViewById(R.id.size_compat_restart_button);
+ restartButton.setOnClickListener(view -> mWindowManager.onRestartButtonClicked());
+ restartButton.setOnLongClickListener(view -> {
+ mWindowManager.onRestartButtonLongClicked();
+ return true;
+ });
+
+ final LinearLayout sizeCompatHint = findViewById(R.id.size_compat_hint);
+ ((TextView) sizeCompatHint.findViewById(R.id.compat_mode_hint_text))
+ .setText(R.string.restart_button_description);
+ sizeCompatHint.setOnClickListener(view -> setSizeCompatHintVisibility(/* show= */ false));
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
new file mode 100644
index 000000000000..997ad04e3b57
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.util.Log;
+import android.view.IWindow;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.SurfaceSession;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Holds view hierarchy of a root surface and helps to inflate and manage layout for compat
+ * controls.
+ */
+class CompatUIWindowManager extends WindowlessWindowManager {
+
+ private static final String TAG = "CompatUIWindowManager";
+
+ private final SyncTransactionQueue mSyncQueue;
+ private final CompatUIController.CompatUICallback mCallback;
+ private final int mDisplayId;
+ private final int mTaskId;
+ private final Rect mStableBounds;
+
+ private Context mContext;
+ private Configuration mTaskConfig;
+ private ShellTaskOrganizer.TaskListener mTaskListener;
+ private DisplayLayout mDisplayLayout;
+
+ @VisibleForTesting
+ boolean mShouldShowHint;
+
+ @Nullable
+ @VisibleForTesting
+ CompatUILayout mCompatUILayout;
+
+ @Nullable
+ private SurfaceControlViewHost mViewHost;
+ @Nullable
+ private SurfaceControl mLeash;
+
+ CompatUIWindowManager(Context context, Configuration taskConfig,
+ SyncTransactionQueue syncQueue, CompatUIController.CompatUICallback callback,
+ int taskId, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
+ boolean hasShownHint) {
+ super(taskConfig, null /* rootSurface */, null /* hostInputToken */);
+ mContext = context;
+ mSyncQueue = syncQueue;
+ mCallback = callback;
+ mTaskConfig = taskConfig;
+ mDisplayId = mContext.getDisplayId();
+ mTaskId = taskId;
+ mTaskListener = taskListener;
+ mDisplayLayout = displayLayout;
+ mShouldShowHint = !hasShownHint;
+ mStableBounds = new Rect();
+ mDisplayLayout.getStableBounds(mStableBounds);
+ }
+
+ @Override
+ public void setConfiguration(Configuration configuration) {
+ super.setConfiguration(configuration);
+ mContext = mContext.createConfigurationContext(configuration);
+ }
+
+ @Override
+ protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+ // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later.
+ final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+ .setContainerLayer()
+ .setName("CompatUILeash")
+ .setHidden(false)
+ .setCallsite("CompatUIWindowManager#attachToParentSurface");
+ attachToParentSurface(builder);
+ mLeash = builder.build();
+ b.setParent(mLeash);
+ }
+
+ /** Creates the layout for compat controls. */
+ void createLayout(boolean show) {
+ if (!show || mCompatUILayout != null) {
+ // Wait until compat controls should be visible.
+ return;
+ }
+
+ initCompatUi();
+ updateSurfacePosition();
+
+ mCallback.onSizeCompatRestartButtonAppeared(mTaskId);
+ }
+
+ /** Called when compat info changed. */
+ void updateCompatInfo(Configuration taskConfig,
+ ShellTaskOrganizer.TaskListener taskListener, boolean show) {
+ final Configuration prevTaskConfig = mTaskConfig;
+ final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener;
+ mTaskConfig = taskConfig;
+ mTaskListener = taskListener;
+
+ // Update configuration.
+ mContext = mContext.createConfigurationContext(taskConfig);
+ setConfiguration(taskConfig);
+
+ if (mCompatUILayout == null || prevTaskListener != taskListener) {
+ // TaskListener changed, recreate the layout for new surface parent.
+ release();
+ createLayout(show);
+ return;
+ }
+
+ if (!taskConfig.windowConfiguration.getBounds()
+ .equals(prevTaskConfig.windowConfiguration.getBounds())) {
+ // Reposition the UI surfaces.
+ updateSurfacePosition();
+ }
+
+ if (taskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection()) {
+ // Update layout for RTL.
+ mCompatUILayout.setLayoutDirection(taskConfig.getLayoutDirection());
+ updateSurfacePosition();
+ }
+ }
+
+ /** Called when the visibility of the UI should change. */
+ void updateVisibility(boolean show) {
+ if (mCompatUILayout == null) {
+ // Layout may not have been created because it was hidden previously.
+ createLayout(show);
+ return;
+ }
+
+ // Hide compat UIs when IME is showing.
+ final int newVisibility = show ? View.VISIBLE : View.GONE;
+ if (mCompatUILayout.getVisibility() != newVisibility) {
+ mCompatUILayout.setVisibility(newVisibility);
+ }
+ }
+
+ /** Called when display layout changed. */
+ void updateDisplayLayout(DisplayLayout displayLayout) {
+ final Rect prevStableBounds = mStableBounds;
+ final Rect curStableBounds = new Rect();
+ displayLayout.getStableBounds(curStableBounds);
+ mDisplayLayout = displayLayout;
+ if (!prevStableBounds.equals(curStableBounds)) {
+ // Stable bounds changed, update UI surface positions.
+ updateSurfacePosition();
+ mStableBounds.set(curStableBounds);
+ }
+ }
+
+ /** Called when it is ready to be placed compat UI surface. */
+ void attachToParentSurface(SurfaceControl.Builder b) {
+ mTaskListener.attachChildSurfaceToTask(mTaskId, b);
+ }
+
+ /** Called when the restart button is clicked. */
+ void onRestartButtonClicked() {
+ mCallback.onSizeCompatRestartButtonClicked(mTaskId);
+ }
+
+ /** Called when the restart button is long clicked. */
+ void onRestartButtonLongClicked() {
+ if (mCompatUILayout == null) {
+ return;
+ }
+ mCompatUILayout.setSizeCompatHintVisibility(/* show= */ true);
+ }
+
+ int getDisplayId() {
+ return mDisplayId;
+ }
+
+ int getTaskId() {
+ return mTaskId;
+ }
+
+ /** Releases the surface control and tears down the view hierarchy. */
+ void release() {
+ mCompatUILayout = null;
+
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+
+ if (mLeash != null) {
+ final SurfaceControl leash = mLeash;
+ mSyncQueue.runInSync(t -> t.remove(leash));
+ mLeash = null;
+ }
+ }
+
+ void relayout() {
+ mViewHost.relayout(getWindowLayoutParams());
+ updateSurfacePosition();
+ }
+
+ @VisibleForTesting
+ void updateSurfacePosition() {
+ if (mCompatUILayout == null || mLeash == null) {
+ return;
+ }
+
+ // Use stable bounds to prevent controls from overlapping with system bars.
+ final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds();
+ final Rect stableBounds = new Rect();
+ mDisplayLayout.getStableBounds(stableBounds);
+ stableBounds.intersect(taskBounds);
+
+ // Position of the button in the container coordinate.
+ final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+ ? stableBounds.left - taskBounds.left
+ : stableBounds.right - taskBounds.left - mCompatUILayout.getMeasuredWidth();
+ final int positionY = stableBounds.bottom - taskBounds.top
+ - mCompatUILayout.getMeasuredHeight();
+
+ updateSurfacePosition(positionX, positionY);
+ }
+
+ private int getLayoutDirection() {
+ return mContext.getResources().getConfiguration().getLayoutDirection();
+ }
+
+ private void updateSurfacePosition(int positionX, int positionY) {
+ mSyncQueue.runInSync(t -> {
+ if (mLeash == null || !mLeash.isValid()) {
+ Log.w(TAG, "The leash has been released.");
+ return;
+ }
+ t.setPosition(mLeash, positionX, positionY);
+ // The compat UI should be the topmost child of the Task in case there can be more
+ // than one children.
+ t.setLayer(mLeash, Integer.MAX_VALUE);
+ });
+ }
+
+ /** Inflates {@link CompatUILayout} on to the root surface. */
+ private void initCompatUi() {
+ if (mViewHost != null) {
+ throw new IllegalStateException(
+ "A UI has already been created with this window manager.");
+ }
+
+ // Construction extracted into the separate methods to allow injection for tests.
+ mViewHost = createSurfaceViewHost();
+ mCompatUILayout = inflateCompatUILayout();
+ mCompatUILayout.inject(this);
+
+ mCompatUILayout.setSizeCompatHintVisibility(mShouldShowHint);
+
+ mViewHost.setView(mCompatUILayout, getWindowLayoutParams());
+
+ // Only show by default for the first time.
+ mShouldShowHint = false;
+ }
+
+ @VisibleForTesting
+ CompatUILayout inflateCompatUILayout() {
+ return (CompatUILayout) LayoutInflater.from(mContext)
+ .inflate(R.layout.compat_ui_layout, null);
+ }
+
+ @VisibleForTesting
+ SurfaceControlViewHost createSurfaceViewHost() {
+ return new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+ }
+
+ /** Gets the layout params. */
+ private WindowManager.LayoutParams getWindowLayoutParams() {
+ // Measure how big the hint is since its size depends on the text size.
+ mCompatUILayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams(
+ // Cannot be wrap_content as this determines the actual window size
+ mCompatUILayout.getMeasuredWidth(), mCompatUILayout.getMeasuredHeight(),
+ TYPE_APPLICATION_OVERLAY,
+ FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL,
+ PixelFormat.TRANSLUCENT);
+ winParams.token = new Binder();
+ winParams.setTitle(CompatUILayout.class.getSimpleName() + mTaskId);
+ winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+ return winParams;
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 7bc8d23d81de..6d158d591011 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -55,6 +55,8 @@ import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellAnimationThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
+import com.android.wm.shell.compatui.CompatUI;
+import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
import com.android.wm.shell.draganddrop.DragAndDrop;
@@ -76,8 +78,6 @@ import com.android.wm.shell.pip.phone.PipAppOpsListener;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
-import com.android.wm.shell.sizecompatui.SizeCompatUIController;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.startingsurface.StartingSurface;
@@ -174,25 +174,25 @@ public abstract class WMShellBaseModule {
@Provides
static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
Context context,
- SizeCompatUIController sizeCompatUI,
+ CompatUIController compatUI,
Optional<RecentTasksController> recentTasksOptional
) {
- return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI, recentTasksOptional);
+ return new ShellTaskOrganizer(mainExecutor, context, compatUI, recentTasksOptional);
}
@WMSingleton
@Provides
- static SizeCompatUI provideSizeCompatUI(SizeCompatUIController sizeCompatUIController) {
- return sizeCompatUIController.asSizeCompatUI();
+ static CompatUI provideCompatUI(CompatUIController compatUIController) {
+ return compatUIController.asCompatUI();
}
@WMSingleton
@Provides
- static SizeCompatUIController provideSizeCompatUIController(Context context,
+ static CompatUIController provideCompatUIController(Context context,
DisplayController displayController, DisplayInsetsController displayInsetsController,
DisplayImeController imeController, SyncTransactionQueue syncQueue,
@ShellMainThread ShellExecutor mainExecutor) {
- return new SizeCompatUIController(context, displayController, displayInsetsController,
+ return new CompatUIController(context, displayController, displayInsetsController,
imeController, syncQueue, mainExecutor);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 854fc60e15e8..f0b2716f05d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -778,8 +778,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
@Override
- public boolean supportSizeCompatUI() {
- // PIP doesn't support size compat.
+ public boolean supportCompatUI() {
+ // PIP doesn't support compat.
return false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java
deleted file mode 100644
index ff6f913207f6..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.sizecompatui;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import androidx.annotation.Nullable;
-
-import com.android.wm.shell.R;
-
-/** Popup to show the hint about the {@link SizeCompatRestartButton}. */
-public class SizeCompatHintPopup extends FrameLayout implements View.OnClickListener {
-
- private SizeCompatUILayout mLayout;
-
- public SizeCompatHintPopup(Context context) {
- super(context);
- }
-
- public SizeCompatHintPopup(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- }
-
- public SizeCompatHintPopup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public SizeCompatHintPopup(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- void inject(SizeCompatUILayout layout) {
- mLayout = layout;
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- final LinearLayout hintPopup = findViewById(R.id.size_compat_hint_popup);
- hintPopup.setOnClickListener(this);
- }
-
- @Override
- public void onClick(View v) {
- mLayout.dismissHint();
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
deleted file mode 100644
index d75fe5173c5f..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.sizecompatui;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageButton;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.wm.shell.R;
-
-/** Button to restart the size compat activity. */
-public class SizeCompatRestartButton extends FrameLayout implements View.OnClickListener,
- View.OnLongClickListener {
-
- private SizeCompatUILayout mLayout;
-
- public SizeCompatRestartButton(@NonNull Context context) {
- super(context);
- }
-
- public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- }
-
- public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs,
- int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs,
- int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- void inject(SizeCompatUILayout layout) {
- mLayout = layout;
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- final ImageButton restartButton = findViewById(R.id.size_compat_restart_button);
- restartButton.setOnClickListener(this);
- restartButton.setOnLongClickListener(this);
- }
-
- @Override
- public void onClick(View v) {
- mLayout.onRestartButtonClicked();
- }
-
- @Override
- public boolean onLongClick(View v) {
- mLayout.onRestartButtonLongClicked();
- return true;
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
deleted file mode 100644
index c35b89af6c1b..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.sizecompatui;
-
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.os.Binder;
-import android.util.Log;
-import android.view.SurfaceControl;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-/**
- * Records and handles layout of size compat UI on a task with size compat activity. Helps to
- * calculate proper bounds when configuration or UI position changes.
- */
-class SizeCompatUILayout {
- private static final String TAG = "SizeCompatUILayout";
-
- final SyncTransactionQueue mSyncQueue;
- private final SizeCompatUIController.SizeCompatUICallback mCallback;
- private Context mContext;
- private Configuration mTaskConfig;
- private final int mDisplayId;
- private final int mTaskId;
- private ShellTaskOrganizer.TaskListener mTaskListener;
- private DisplayLayout mDisplayLayout;
- private final Rect mStableBounds;
- private final int mButtonWidth;
- private final int mButtonHeight;
- private final int mPopupOffsetX;
- private final int mPopupOffsetY;
-
- @VisibleForTesting
- final SizeCompatUIWindowManager mButtonWindowManager;
- @VisibleForTesting
- @Nullable
- SizeCompatUIWindowManager mHintWindowManager;
- @VisibleForTesting
- @Nullable
- SizeCompatRestartButton mButton;
- @VisibleForTesting
- @Nullable
- SizeCompatHintPopup mHint;
- @VisibleForTesting
- boolean mShouldShowHint;
-
- SizeCompatUILayout(SyncTransactionQueue syncQueue,
- SizeCompatUIController.SizeCompatUICallback callback, Context context,
- Configuration taskConfig, int taskId, ShellTaskOrganizer.TaskListener taskListener,
- DisplayLayout displayLayout, boolean hasShownHint) {
- mSyncQueue = syncQueue;
- mCallback = callback;
- mContext = context.createConfigurationContext(taskConfig);
- mTaskConfig = taskConfig;
- mDisplayId = mContext.getDisplayId();
- mTaskId = taskId;
- mTaskListener = taskListener;
- mDisplayLayout = displayLayout;
- mShouldShowHint = !hasShownHint;
- mButtonWindowManager = new SizeCompatUIWindowManager(mContext, taskConfig, this);
-
- mStableBounds = new Rect();
- mDisplayLayout.getStableBounds(mStableBounds);
-
- final Resources resources = mContext.getResources();
- mButtonWidth = resources.getDimensionPixelSize(R.dimen.size_compat_button_width);
- mButtonHeight = resources.getDimensionPixelSize(R.dimen.size_compat_button_height);
- mPopupOffsetX = (mButtonWidth / 2) - resources.getDimensionPixelSize(
- R.dimen.size_compat_hint_corner_radius) - (resources.getDimensionPixelSize(
- R.dimen.size_compat_hint_point_width) / 2);
- mPopupOffsetY = mButtonHeight;
- }
-
- /** Creates the activity restart button window. */
- void createSizeCompatButton(boolean show) {
- if (!show || mButton != null) {
- // Wait until button should be visible.
- return;
- }
- mButton = mButtonWindowManager.createSizeCompatButton();
- updateButtonSurfacePosition();
-
- if (mShouldShowHint) {
- // Only show by default for the first time.
- mShouldShowHint = false;
- createSizeCompatHint();
- }
-
- mCallback.onSizeCompatRestartButtonAppeared(mTaskId);
- }
-
- /** Creates the restart button hint window. */
- private void createSizeCompatHint() {
- if (mHint != null) {
- // Hint already shown.
- return;
- }
- mHintWindowManager = createHintWindowManager();
- mHint = mHintWindowManager.createSizeCompatHint();
- updateHintSurfacePosition();
- }
-
- @VisibleForTesting
- SizeCompatUIWindowManager createHintWindowManager() {
- return new SizeCompatUIWindowManager(mContext, mTaskConfig, this);
- }
-
- /** Dismisses the hint window. */
- void dismissHint() {
- mHint = null;
- if (mHintWindowManager != null) {
- mHintWindowManager.release();
- mHintWindowManager = null;
- }
- }
-
- /** Releases the UI windows. */
- void release() {
- dismissHint();
- mButton = null;
- mButtonWindowManager.release();
- }
-
- /** Called when size compat info changed. */
- void updateSizeCompatInfo(Configuration taskConfig,
- ShellTaskOrganizer.TaskListener taskListener, boolean show) {
- final Configuration prevTaskConfig = mTaskConfig;
- final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener;
- mTaskConfig = taskConfig;
- mTaskListener = taskListener;
-
- // Update configuration.
- mContext = mContext.createConfigurationContext(taskConfig);
- mButtonWindowManager.setConfiguration(taskConfig);
- if (mHintWindowManager != null) {
- mHintWindowManager.setConfiguration(taskConfig);
- }
-
- if (mButton == null || prevTaskListener != taskListener) {
- // TaskListener changed, recreate the button for new surface parent.
- release();
- createSizeCompatButton(show);
- return;
- }
-
- if (!taskConfig.windowConfiguration.getBounds()
- .equals(prevTaskConfig.windowConfiguration.getBounds())) {
- // Reposition the UI surfaces.
- updateAllSurfacePositions();
- }
-
- if (taskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection()) {
- // Update layout for RTL.
- mButton.setLayoutDirection(taskConfig.getLayoutDirection());
- updateButtonSurfacePosition();
- if (mHint != null) {
- mHint.setLayoutDirection(taskConfig.getLayoutDirection());
- updateHintSurfacePosition();
- }
- }
- }
-
- /** Called when display layout changed. */
- void updateDisplayLayout(DisplayLayout displayLayout) {
- final Rect prevStableBounds = mStableBounds;
- final Rect curStableBounds = new Rect();
- displayLayout.getStableBounds(curStableBounds);
- mDisplayLayout = displayLayout;
- if (!prevStableBounds.equals(curStableBounds)) {
- // Stable bounds changed, update UI surface positions.
- updateAllSurfacePositions();
- mStableBounds.set(curStableBounds);
- }
- }
-
- /** Called when the visibility of the UI should change. */
- void updateVisibility(boolean show) {
- if (mButton == null) {
- // Button may not have been created because it was hidden previously.
- createSizeCompatButton(show);
- return;
- }
-
- // Hide size compat UIs when IME is showing.
- final int newVisibility = show ? View.VISIBLE : View.GONE;
- if (mButton.getVisibility() != newVisibility) {
- mButton.setVisibility(newVisibility);
- }
- if (mHint != null && mHint.getVisibility() != newVisibility) {
- mHint.setVisibility(newVisibility);
- }
- }
-
- /** Gets the layout params for restart button. */
- WindowManager.LayoutParams getButtonWindowLayoutParams() {
- final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams(
- // Cannot be wrap_content as this determines the actual window size
- mButtonWidth, mButtonHeight,
- TYPE_APPLICATION_OVERLAY,
- FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL,
- PixelFormat.TRANSLUCENT);
- winParams.token = new Binder();
- winParams.setTitle(SizeCompatRestartButton.class.getSimpleName() + getTaskId());
- winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
- return winParams;
- }
-
- /** Gets the layout params for hint popup. */
- WindowManager.LayoutParams getHintWindowLayoutParams(SizeCompatHintPopup hint) {
- final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams(
- // Cannot be wrap_content as this determines the actual window size
- hint.getMeasuredWidth(), hint.getMeasuredHeight(),
- TYPE_APPLICATION_OVERLAY,
- FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL,
- PixelFormat.TRANSLUCENT);
- winParams.token = new Binder();
- winParams.setTitle(SizeCompatHintPopup.class.getSimpleName() + getTaskId());
- winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
- winParams.windowAnimations = android.R.style.Animation_InputMethod;
- return winParams;
- }
-
- /** Called when it is ready to be placed size compat UI surface. */
- void attachToParentSurface(SurfaceControl.Builder b) {
- mTaskListener.attachChildSurfaceToTask(mTaskId, b);
- }
-
- /** Called when the restart button is clicked. */
- void onRestartButtonClicked() {
- mCallback.onSizeCompatRestartButtonClicked(mTaskId);
- }
-
- /** Called when the restart button is long clicked. */
- void onRestartButtonLongClicked() {
- createSizeCompatHint();
- }
-
- private void updateAllSurfacePositions() {
- updateButtonSurfacePosition();
- updateHintSurfacePosition();
- }
-
- @VisibleForTesting
- void updateButtonSurfacePosition() {
- if (mButton == null || mButtonWindowManager.getSurfaceControl() == null) {
- return;
- }
- final SurfaceControl leash = mButtonWindowManager.getSurfaceControl();
-
- // Use stable bounds to prevent the button from overlapping with system bars.
- final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds();
- final Rect stableBounds = new Rect();
- mDisplayLayout.getStableBounds(stableBounds);
- stableBounds.intersect(taskBounds);
-
- // Position of the button in the container coordinate.
- final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
- ? stableBounds.left - taskBounds.left
- : stableBounds.right - taskBounds.left - mButtonWidth;
- final int positionY = stableBounds.bottom - taskBounds.top - mButtonHeight;
-
- updateSurfacePosition(leash, positionX, positionY);
- }
-
- @VisibleForTesting
- void updateHintSurfacePosition() {
- if (mHint == null || mHintWindowManager == null
- || mHintWindowManager.getSurfaceControl() == null) {
- return;
- }
- final SurfaceControl leash = mHintWindowManager.getSurfaceControl();
-
- // Use stable bounds to prevent the hint from overlapping with system bars.
- final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds();
- final Rect stableBounds = new Rect();
- mDisplayLayout.getStableBounds(stableBounds);
- stableBounds.intersect(taskBounds);
-
- // Position of the hint in the container coordinate.
- final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
- ? stableBounds.left - taskBounds.left + mPopupOffsetX
- : stableBounds.right - taskBounds.left - mPopupOffsetX - mHint.getMeasuredWidth();
- final int positionY =
- stableBounds.bottom - taskBounds.top - mPopupOffsetY - mHint.getMeasuredHeight();
-
- updateSurfacePosition(leash, positionX, positionY);
- }
-
- private void updateSurfacePosition(SurfaceControl leash, int positionX, int positionY) {
- mSyncQueue.runInSync(t -> {
- if (!leash.isValid()) {
- Log.w(TAG, "The leash has been released.");
- return;
- }
- t.setPosition(leash, positionX, positionY);
- // The size compat UI should be the topmost child of the Task in case there can be more
- // than one children.
- t.setLayer(leash, Integer.MAX_VALUE);
- });
- }
-
- int getDisplayId() {
- return mDisplayId;
- }
-
- int getTaskId() {
- return mTaskId;
- }
-
- private int getLayoutDirection() {
- return mContext.getResources().getConfiguration().getLayoutDirection();
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
deleted file mode 100644
index 82f69c3e2985..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.sizecompatui;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.view.IWindow;
-import android.view.LayoutInflater;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.SurfaceSession;
-import android.view.View;
-import android.view.WindowlessWindowManager;
-
-import com.android.wm.shell.R;
-
-/**
- * Holds view hierarchy of a root surface and helps to inflate {@link SizeCompatRestartButton} or
- * {@link SizeCompatHintPopup}.
- */
-class SizeCompatUIWindowManager extends WindowlessWindowManager {
-
- private Context mContext;
- private final SizeCompatUILayout mLayout;
-
- @Nullable
- private SurfaceControlViewHost mViewHost;
- @Nullable
- private SurfaceControl mLeash;
-
- SizeCompatUIWindowManager(Context context, Configuration config, SizeCompatUILayout layout) {
- super(config, null /* rootSurface */, null /* hostInputToken */);
- mContext = context;
- mLayout = layout;
- }
-
- @Override
- public void setConfiguration(Configuration configuration) {
- super.setConfiguration(configuration);
- mContext = mContext.createConfigurationContext(configuration);
- }
-
- @Override
- protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
- // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later.
- final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
- .setContainerLayer()
- .setName("SizeCompatUILeash")
- .setHidden(false)
- .setCallsite("SizeCompatUIWindowManager#attachToParentSurface");
- mLayout.attachToParentSurface(builder);
- mLeash = builder.build();
- b.setParent(mLeash);
- }
-
- /** Inflates {@link SizeCompatRestartButton} on to the root surface. */
- SizeCompatRestartButton createSizeCompatButton() {
- if (mViewHost != null) {
- throw new IllegalStateException(
- "A UI has already been created with this window manager.");
- }
-
- mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
-
- final SizeCompatRestartButton button = (SizeCompatRestartButton)
- LayoutInflater.from(mContext).inflate(R.layout.size_compat_ui, null);
- button.inject(mLayout);
- mViewHost.setView(button, mLayout.getButtonWindowLayoutParams());
- return button;
- }
-
- /** Inflates {@link SizeCompatHintPopup} on to the root surface. */
- SizeCompatHintPopup createSizeCompatHint() {
- if (mViewHost != null) {
- throw new IllegalStateException(
- "A UI has already been created with this window manager.");
- }
-
- mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
-
- final SizeCompatHintPopup hint = (SizeCompatHintPopup)
- LayoutInflater.from(mContext).inflate(R.layout.size_compat_mode_hint, null);
- // Measure how big the hint is.
- hint.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
- hint.inject(mLayout);
- mViewHost.setView(hint, mLayout.getHintWindowLayoutParams(hint));
- return hint;
- }
-
- /** Releases the surface control and tears down the view hierarchy. */
- void release() {
- if (mViewHost != null) {
- mViewHost.release();
- mViewHost = null;
- }
-
- if (mLeash != null) {
- final SurfaceControl leash = mLeash;
- mLayout.mSyncQueue.runInSync(t -> t.remove(leash));
- mLeash = null;
- }
- }
-
- /**
- * Gets {@link SurfaceControl} of the surface holding size compat UI view. @return {@code null}
- * if not feasible.
- */
- @Nullable
- SurfaceControl getSurfaceControl() {
- return mLeash;
- }
-}
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 ed5de0d698f3..cdaa54c7266d 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
@@ -342,12 +342,12 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
sideOptions = sideOptions != null ? sideOptions : new Bundle();
setSideStagePosition(sidePosition, wct);
+ mSplitLayout.setDivideRatio(splitRatio);
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
mSideStage.setBounds(getSideStageBounds(), wct);
- mSplitLayout.setDivideRatio(splitRatio);
// Make sure the launch options will put tasks in the corresponding split roots
addActivityOptions(mainOptions, mMainStage);
addActivityOptions(sideOptions, mSideStage);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 6643ca176280..4ecc0b685626 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -380,9 +380,7 @@ public class TaskSnapshotWindow {
}
private void drawSizeMatchSnapshot() {
- GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
- mSnapshot.getHardwareBuffer());
- mTransaction.setBuffer(mSurfaceControl, graphicBuffer)
+ mTransaction.setBuffer(mSurfaceControl, mSnapshot.getHardwareBuffer())
.setColorSpace(mSurfaceControl, mSnapshot.getColorSpace())
.apply();
}
@@ -428,20 +426,20 @@ public class TaskSnapshotWindow {
// Scale the mismatch dimensions to fill the task bounds
mSnapshotMatrix.setRectToRect(mTmpSnapshotSize, mTmpDstFrame, Matrix.ScaleToFit.FILL);
mTransaction.setMatrix(childSurfaceControl, mSnapshotMatrix, mTmpFloat9);
- GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
- mSnapshot.getHardwareBuffer());
mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace());
- mTransaction.setBuffer(childSurfaceControl, graphicBuffer);
+ mTransaction.setBuffer(childSurfaceControl, mSnapshot.getHardwareBuffer());
if (aspectRatioMismatch) {
GraphicBuffer background = GraphicBuffer.create(mFrame.width(), mFrame.height(),
PixelFormat.RGBA_8888,
GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
| GraphicBuffer.USAGE_SW_WRITE_RARELY);
+ // TODO: Support this on HardwareBuffer
final Canvas c = background.lockCanvas();
drawBackgroundAndBars(c, frame);
background.unlockCanvasAndPost(c);
- mTransaction.setBuffer(mSurfaceControl, background);
+ mTransaction.setBuffer(mSurfaceControl,
+ HardwareBuffer.createFromGraphicBuffer(background));
}
mTransaction.apply();
childSurfaceControl.release();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 1fcbf14fb732..a3b98a8fc880 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -56,7 +56,7 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.sizecompatui.SizeCompatUIController;
+import com.android.wm.shell.compatui.CompatUIController;
import org.junit.Before;
import org.junit.Test;
@@ -82,7 +82,7 @@ public class ShellTaskOrganizerTests {
@Mock
private Context mContext;
@Mock
- private SizeCompatUIController mSizeCompatUI;
+ private CompatUIController mCompatUI;
ShellTaskOrganizer mOrganizer;
private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
@@ -132,7 +132,7 @@ public class ShellTaskOrganizerTests {
.when(mTaskOrganizerController).registerTaskOrganizer(any());
} catch (RemoteException e) {}
mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext,
- mSizeCompatUI, Optional.empty()));
+ mCompatUI, Optional.empty()));
}
@Test
@@ -334,34 +334,34 @@ public class ShellTaskOrganizerTests {
mOrganizer.onTaskAppeared(taskInfo1, null);
// sizeCompatActivity is null if top activity is not in size compat.
- verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
+ verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
null /* taskConfig */, null /* taskListener */);
// sizeCompatActivity is non-null if top activity is in size compat.
- clearInvocations(mSizeCompatUI);
+ clearInvocations(mCompatUI);
final RunningTaskInfo taskInfo2 =
createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
taskInfo2.displayId = taskInfo1.displayId;
taskInfo2.topActivityInSizeCompat = true;
taskInfo2.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo2);
- verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
+ verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
taskInfo1.configuration, taskListener);
// Not show size compat UI if task is not visible.
- clearInvocations(mSizeCompatUI);
+ clearInvocations(mCompatUI);
final RunningTaskInfo taskInfo3 =
createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
taskInfo3.displayId = taskInfo1.displayId;
taskInfo3.topActivityInSizeCompat = true;
taskInfo3.isVisible = false;
mOrganizer.onTaskInfoChanged(taskInfo3);
- verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
+ verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
null /* taskConfig */, null /* taskListener */);
- clearInvocations(mSizeCompatUI);
+ clearInvocations(mCompatUI);
mOrganizer.onTaskVanished(taskInfo1);
- verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
+ verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
null /* taskConfig */, null /* taskListener */);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index bc701d0c70bc..8bc1223cfd64 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -39,6 +39,7 @@ import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.util.Log;
import android.util.Pair;
import android.view.WindowManager;
@@ -913,6 +914,31 @@ public class BubbleDataTest extends ShellTestCase {
assertSelectionChangedTo(mBubbleA2);
}
+ /**
+ * - have a maxed out bubble stack & all of the bubbles have been recently accessed
+ * - bubble a notification that was posted before any of those bubbles were accessed
+ * => that bubble should be added
+ *
+ */
+ @Test
+ public void test_addOldNotifWithNewerBubbles() {
+ sendUpdatedEntryAtTime(mEntryA1, 2000);
+ sendUpdatedEntryAtTime(mEntryA2, 3000);
+ sendUpdatedEntryAtTime(mEntryA3, 4000);
+ sendUpdatedEntryAtTime(mEntryB1, 5000);
+ sendUpdatedEntryAtTime(mEntryB2, 6000);
+
+ mBubbleData.setListener(mListener);
+ sendUpdatedEntryAtTime(mEntryB3, 1000 /* postTime */, 7000 /* currentTime */);
+ verifyUpdateReceived();
+
+ // B3 is in the stack
+ assertThat(mBubbleData.getBubbleInStackWithKey(mBubbleB3.getKey())).isNotNull();
+ // A1 is the oldest so it's in the overflow
+ assertThat(mBubbleData.getOverflowBubbleWithKey(mEntryA1.getKey())).isNotNull();
+ assertOrderChangedTo(mBubbleB3, mBubbleB2, mBubbleB1, mBubbleA3, mBubbleA2);
+ }
+
private void verifyUpdateReceived() {
verify(mListener).applyUpdate(mUpdateCaptor.capture());
reset(mListener);
@@ -1014,6 +1040,12 @@ public class BubbleDataTest extends ShellTestCase {
}
private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime) {
+ setCurrentTime(postTime);
+ sendUpdatedEntryAtTime(entry, postTime, true /* isTextChanged */);
+ }
+
+ private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime, long currentTime) {
+ setCurrentTime(currentTime);
sendUpdatedEntryAtTime(entry, postTime, true /* isTextChanged */);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 453050fcfab4..83d5f04b7cdb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -24,6 +24,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -101,14 +102,21 @@ public class SplitLayoutTests extends ShellTestCase {
@Test
public void testSetDividePosition() {
- mSplitLayout.setDividePosition(anyInt());
+ mSplitLayout.setDividePosition(100, false /* applyLayoutChange */);
+ assertThat(mSplitLayout.getDividePosition()).isEqualTo(100);
+ verify(mSplitLayoutHandler, never()).onLayoutSizeChanged(any(SplitLayout.class));
+
+ mSplitLayout.setDividePosition(200, true /* applyLayoutChange */);
+ assertThat(mSplitLayout.getDividePosition()).isEqualTo(200);
verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class));
}
@Test
public void testSetDivideRatio() {
+ mSplitLayout.setDividePosition(200, false /* applyLayoutChange */);
mSplitLayout.setDivideRatio(0.5f);
- verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class));
+ assertThat(mSplitLayout.getDividePosition()).isEqualTo(
+ mSplitLayout.mDividerSnapAlgorithm.getMiddleTarget().position);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 877b19223bf6..f622edb7f134 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.sizecompatui;
+package com.android.wm.shell.compatui;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
@@ -56,18 +56,18 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
- * Tests for {@link SizeCompatUIController}.
+ * Tests for {@link CompatUIController}.
*
* Build/Install/Run:
- * atest WMShellUnitTests:SizeCompatUIControllerTest
+ * atest WMShellUnitTests:CompatUIControllerTest
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class SizeCompatUIControllerTest extends ShellTestCase {
+public class CompatUIControllerTest extends ShellTestCase {
private static final int DISPLAY_ID = 0;
private static final int TASK_ID = 12;
- private SizeCompatUIController mController;
+ private CompatUIController mController;
private @Mock DisplayController mMockDisplayController;
private @Mock DisplayInsetsController mMockDisplayInsetsController;
private @Mock DisplayLayout mMockDisplayLayout;
@@ -75,7 +75,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase {
private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener;
private @Mock SyncTransactionQueue mMockSyncQueue;
private @Mock ShellExecutor mMockExecutor;
- private @Mock SizeCompatUILayout mMockLayout;
+ private @Mock CompatUIWindowManager mMockLayout;
@Captor
ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
@@ -87,10 +87,10 @@ public class SizeCompatUIControllerTest extends ShellTestCase {
doReturn(mMockDisplayLayout).when(mMockDisplayController).getDisplayLayout(anyInt());
doReturn(DISPLAY_ID).when(mMockLayout).getDisplayId();
doReturn(TASK_ID).when(mMockLayout).getTaskId();
- mController = new SizeCompatUIController(mContext, mMockDisplayController,
+ mController = new CompatUIController(mContext, mMockDisplayController,
mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor) {
@Override
- SizeCompatUILayout createLayout(Context context, int displayId, int taskId,
+ CompatUIWindowManager createLayout(Context context, int displayId, int taskId,
Configuration taskConfig, ShellTaskOrganizer.TaskListener taskListener) {
return mMockLayout;
}
@@ -105,24 +105,24 @@ public class SizeCompatUIControllerTest extends ShellTestCase {
}
@Test
- public void testOnSizeCompatInfoChanged() {
+ public void testOnCompatInfoChanged() {
final Configuration taskConfig = new Configuration();
// Verify that the restart button is added with non-null size compat info.
- mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+ mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
verify(mController).createLayout(any(), eq(DISPLAY_ID), eq(TASK_ID), eq(taskConfig),
eq(mMockTaskListener));
// Verify that the restart button is updated with non-null new size compat info.
final Configuration newTaskConfig = new Configuration();
- mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, newTaskConfig, mMockTaskListener);
+ mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, newTaskConfig, mMockTaskListener);
- verify(mMockLayout).updateSizeCompatInfo(taskConfig, mMockTaskListener,
+ verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener,
true /* show */);
// Verify that the restart button is removed with null size compat info.
- mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, null, mMockTaskListener);
+ mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, null, mMockTaskListener);
verify(mMockLayout).release();
}
@@ -140,7 +140,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase {
public void testOnDisplayRemoved() {
mController.onDisplayAdded(DISPLAY_ID);
final Configuration taskConfig = new Configuration();
- mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
+ mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
mMockTaskListener);
mController.onDisplayRemoved(DISPLAY_ID + 1);
@@ -158,7 +158,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase {
@Test
public void testOnDisplayConfigurationChanged() {
final Configuration taskConfig = new Configuration();
- mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
+ mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
mMockTaskListener);
final Configuration newTaskConfig = new Configuration();
@@ -175,7 +175,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase {
public void testInsetsChanged() {
mController.onDisplayAdded(DISPLAY_ID);
final Configuration taskConfig = new Configuration();
- mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
+ mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
mMockTaskListener);
InsetsState insetsState = new InsetsState();
InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
@@ -197,7 +197,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase {
@Test
public void testChangeButtonVisibilityOnImeShowHide() {
final Configuration taskConfig = new Configuration();
- mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+ mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
// Verify that the restart button is hidden after IME is showing.
mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
@@ -205,9 +205,9 @@ public class SizeCompatUIControllerTest extends ShellTestCase {
verify(mMockLayout).updateVisibility(false);
// Verify button remains hidden while IME is showing.
- mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+ mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
- verify(mMockLayout).updateSizeCompatInfo(taskConfig, mMockTaskListener,
+ verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener,
false /* show */);
// Verify button is shown after IME is hidden.
@@ -219,7 +219,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase {
@Test
public void testChangeButtonVisibilityOnKeyguardOccludedChanged() {
final Configuration taskConfig = new Configuration();
- mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+ mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
// Verify that the restart button is hidden after keyguard becomes occluded.
mController.onKeyguardOccludedChanged(true);
@@ -227,9 +227,9 @@ public class SizeCompatUIControllerTest extends ShellTestCase {
verify(mMockLayout).updateVisibility(false);
// Verify button remains hidden while keyguard is occluded.
- mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+ mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
- verify(mMockLayout).updateSizeCompatInfo(taskConfig, mMockTaskListener,
+ verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener,
false /* show */);
// Verify button is shown after keyguard becomes not occluded.
@@ -241,7 +241,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase {
@Test
public void testButtonRemainsHiddenOnKeyguardOccludedFalseWhenImeIsShowing() {
final Configuration taskConfig = new Configuration();
- mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+ mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
mController.onKeyguardOccludedChanged(true);
@@ -264,7 +264,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase {
@Test
public void testButtonRemainsHiddenOnImeHideWhenKeyguardIsOccluded() {
final Configuration taskConfig = new Configuration();
- mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+ mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
mController.onKeyguardOccludedChanged(true);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index a20a5e9e8d91..2c3987bc358d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -14,17 +14,20 @@
* limitations under the License.
*/
-package com.android.wm.shell.sizecompatui;
+package com.android.wm.shell.compatui;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import android.content.res.Configuration;
import android.testing.AndroidTestingRunner;
import android.view.LayoutInflater;
+import android.view.SurfaceControlViewHost;
import android.widget.ImageButton;
+import android.widget.LinearLayout;
import androidx.test.filters.SmallTest;
@@ -41,55 +44,68 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
- * Tests for {@link SizeCompatRestartButton}.
+ * Tests for {@link CompatUILayout}.
*
* Build/Install/Run:
- * atest WMShellUnitTests:SizeCompatRestartButtonTest
+ * atest WMShellUnitTests:CompatUILayoutTest
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class SizeCompatRestartButtonTest extends ShellTestCase {
+public class CompatUILayoutTest extends ShellTestCase {
private static final int TASK_ID = 1;
@Mock private SyncTransactionQueue mSyncTransactionQueue;
- @Mock private SizeCompatUIController.SizeCompatUICallback mCallback;
+ @Mock private CompatUIController.CompatUICallback mCallback;
@Mock private ShellTaskOrganizer.TaskListener mTaskListener;
- @Mock private DisplayLayout mDisplayLayout;
+ @Mock private SurfaceControlViewHost mViewHost;
- private SizeCompatUILayout mLayout;
- private SizeCompatRestartButton mButton;
+ private CompatUIWindowManager mWindowManager;
+ private CompatUILayout mCompatUILayout;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mCallback, mContext,
- new Configuration(), TASK_ID, mTaskListener, mDisplayLayout,
+ mWindowManager = new CompatUIWindowManager(mContext, new Configuration(),
+ mSyncTransactionQueue, mCallback, TASK_ID, mTaskListener, new DisplayLayout(),
false /* hasShownHint */);
- mButton = (SizeCompatRestartButton)
- LayoutInflater.from(mContext).inflate(R.layout.size_compat_ui, null);
- mButton.inject(mLayout);
- spyOn(mLayout);
+ mCompatUILayout = (CompatUILayout)
+ LayoutInflater.from(mContext).inflate(R.layout.compat_ui_layout, null);
+ mCompatUILayout.inject(mWindowManager);
+
+ spyOn(mWindowManager);
+ spyOn(mCompatUILayout);
+ doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost();
}
@Test
- public void testOnClick() {
- final ImageButton button = mButton.findViewById(R.id.size_compat_restart_button);
+ public void testOnClickForRestartButton() {
+ final ImageButton button = mCompatUILayout.findViewById(R.id.size_compat_restart_button);
button.performClick();
- verify(mLayout).onRestartButtonClicked();
+ verify(mWindowManager).onRestartButtonClicked();
+ doReturn(mCompatUILayout).when(mWindowManager).inflateCompatUILayout();
verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID);
}
@Test
- public void testOnLongClick() {
- doNothing().when(mLayout).onRestartButtonLongClicked();
+ public void testOnLongClickForRestartButton() {
+ doNothing().when(mWindowManager).onRestartButtonLongClicked();
- final ImageButton button = mButton.findViewById(R.id.size_compat_restart_button);
+ final ImageButton button = mCompatUILayout.findViewById(R.id.size_compat_restart_button);
button.performLongClick();
- verify(mLayout).onRestartButtonLongClicked();
+ verify(mWindowManager).onRestartButtonLongClicked();
+ }
+
+ @Test
+ public void testOnClickForSizeCompatHint() {
+ mWindowManager.createLayout(true /* show */);
+ final LinearLayout sizeCompatHint = mCompatUILayout.findViewById(R.id.size_compat_hint);
+ sizeCompatHint.performClick();
+
+ verify(mCompatUILayout).setSizeCompatHintVisibility(/* show= */ false);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
new file mode 100644
index 000000000000..d5dcf2e11a46
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.view.DisplayInfo;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link CompatUIWindowManager}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:CompatUIWindowManagerTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class CompatUIWindowManagerTest extends ShellTestCase {
+
+ private static final int TASK_ID = 1;
+
+ @Mock private SyncTransactionQueue mSyncTransactionQueue;
+ @Mock private CompatUIController.CompatUICallback mCallback;
+ @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
+ @Mock private CompatUILayout mCompatUILayout;
+ @Mock private SurfaceControlViewHost mViewHost;
+ private Configuration mTaskConfig;
+
+ private CompatUIWindowManager mWindowManager;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mTaskConfig = new Configuration();
+
+ mWindowManager = new CompatUIWindowManager(mContext, new Configuration(),
+ mSyncTransactionQueue, mCallback, TASK_ID, mTaskListener, new DisplayLayout(),
+ false /* hasShownHint */);
+
+ spyOn(mWindowManager);
+ doReturn(mCompatUILayout).when(mWindowManager).inflateCompatUILayout();
+ doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost();
+ }
+
+ @Test
+ public void testCreateSizeCompatButton() {
+ // Not create layout if show is false.
+ mWindowManager.createLayout(false /* show */);
+
+ verify(mWindowManager, never()).inflateCompatUILayout();
+
+ // Not create hint popup.
+ mWindowManager.mShouldShowHint = false;
+ mWindowManager.createLayout(true /* show */);
+
+ verify(mWindowManager).inflateCompatUILayout();
+ verify(mCompatUILayout).setSizeCompatHintVisibility(false /* show */);
+
+ // Create hint popup.
+ mWindowManager.release();
+ mWindowManager.mShouldShowHint = true;
+ mWindowManager.createLayout(true /* show */);
+
+ verify(mWindowManager, times(2)).inflateCompatUILayout();
+ assertNotNull(mCompatUILayout);
+ verify(mCompatUILayout).setSizeCompatHintVisibility(true /* show */);
+ assertFalse(mWindowManager.mShouldShowHint);
+ }
+
+ @Test
+ public void testRelease() {
+ mWindowManager.createLayout(true /* show */);
+
+ verify(mWindowManager).inflateCompatUILayout();
+
+ mWindowManager.release();
+
+ verify(mViewHost).release();
+ }
+
+ @Test
+ public void testUpdateCompatInfo() {
+ mWindowManager.createLayout(true /* show */);
+
+ // No diff
+ clearInvocations(mWindowManager);
+ mWindowManager.updateCompatInfo(mTaskConfig, mTaskListener, true /* show */);
+
+ verify(mWindowManager, never()).updateSurfacePosition();
+ verify(mWindowManager, never()).release();
+ verify(mWindowManager, never()).createLayout(anyBoolean());
+
+ // Change task listener, recreate button.
+ clearInvocations(mWindowManager);
+ final ShellTaskOrganizer.TaskListener newTaskListener = mock(
+ ShellTaskOrganizer.TaskListener.class);
+ mWindowManager.updateCompatInfo(mTaskConfig, newTaskListener,
+ true /* show */);
+
+ verify(mWindowManager).release();
+ verify(mWindowManager).createLayout(anyBoolean());
+
+ // Change task bounds, update position.
+ clearInvocations(mWindowManager);
+ final Configuration newTaskConfiguration = new Configuration();
+ newTaskConfiguration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000));
+ mWindowManager.updateCompatInfo(newTaskConfiguration, newTaskListener,
+ true /* show */);
+
+ verify(mWindowManager).updateSurfacePosition();
+ }
+
+ @Test
+ public void testUpdateDisplayLayout() {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = 1000;
+ displayInfo.logicalHeight = 2000;
+ final DisplayLayout displayLayout1 = new DisplayLayout(displayInfo,
+ mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false);
+
+ mWindowManager.updateDisplayLayout(displayLayout1);
+ verify(mWindowManager).updateSurfacePosition();
+
+ // No update if the display bounds is the same.
+ clearInvocations(mWindowManager);
+ final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo,
+ mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false);
+ mWindowManager.updateDisplayLayout(displayLayout2);
+ verify(mWindowManager, never()).updateSurfacePosition();
+ }
+
+ @Test
+ public void testUpdateDisplayLayoutInsets() {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = 1000;
+ displayInfo.logicalHeight = 2000;
+ final DisplayLayout displayLayout = new DisplayLayout(displayInfo,
+ mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false);
+
+ mWindowManager.updateDisplayLayout(displayLayout);
+ verify(mWindowManager).updateSurfacePosition();
+
+ // Update if the insets change on the existing display layout
+ clearInvocations(mWindowManager);
+ InsetsState insetsState = new InsetsState();
+ InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
+ insetsSource.setFrame(0, 0, 1000, 1000);
+ insetsState.addSource(insetsSource);
+ displayLayout.setInsets(mContext.getResources(), insetsState);
+ mWindowManager.updateDisplayLayout(displayLayout);
+ verify(mWindowManager).updateSurfacePosition();
+ }
+
+ @Test
+ public void testUpdateVisibility() {
+ // Create button if it is not created.
+ mWindowManager.mCompatUILayout = null;
+ mWindowManager.updateVisibility(true /* show */);
+
+ verify(mWindowManager).createLayout(true /* show */);
+
+ // Hide button.
+ clearInvocations(mWindowManager);
+ doReturn(View.VISIBLE).when(mCompatUILayout).getVisibility();
+ mWindowManager.updateVisibility(false /* show */);
+
+ verify(mWindowManager, never()).createLayout(anyBoolean());
+ verify(mCompatUILayout).setVisibility(View.GONE);
+
+ // Show button.
+ doReturn(View.GONE).when(mCompatUILayout).getVisibility();
+ mWindowManager.updateVisibility(true /* show */);
+
+ verify(mWindowManager, never()).createLayout(anyBoolean());
+ verify(mCompatUILayout).setVisibility(View.VISIBLE);
+ }
+
+ @Test
+ public void testAttachToParentSurface() {
+ final SurfaceControl.Builder b = new SurfaceControl.Builder();
+ mWindowManager.attachToParentSurface(b);
+
+ verify(mTaskListener).attachChildSurfaceToTask(TASK_ID, b);
+ }
+
+ @Test
+ public void testOnRestartButtonClicked() {
+ mWindowManager.onRestartButtonClicked();
+
+ verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID);
+ }
+
+ @Test
+ public void testOnRestartButtonLongClicked_showHint() {
+ // Not create hint popup.
+ mWindowManager.mShouldShowHint = false;
+ mWindowManager.createLayout(true /* show */);
+
+ verify(mWindowManager).inflateCompatUILayout();
+ verify(mCompatUILayout).setSizeCompatHintVisibility(false /* show */);
+
+ mWindowManager.onRestartButtonLongClicked();
+
+ verify(mCompatUILayout).setSizeCompatHintVisibility(true /* show */);
+ }
+
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java
deleted file mode 100644
index 3a14a336190d..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.sizecompatui;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.verify;
-
-import android.content.res.Configuration;
-import android.testing.AndroidTestingRunner;
-import android.view.LayoutInflater;
-import android.widget.LinearLayout;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link SizeCompatHintPopup}.
- *
- * Build/Install/Run:
- * atest WMShellUnitTests:SizeCompatHintPopupTest
- */
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-public class SizeCompatHintPopupTest extends ShellTestCase {
-
- @Mock private SyncTransactionQueue mSyncTransactionQueue;
- @Mock private SizeCompatUIController.SizeCompatUICallback mCallback;
- @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
- @Mock private DisplayLayout mDisplayLayout;
-
- private SizeCompatUILayout mLayout;
- private SizeCompatHintPopup mHint;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- final int taskId = 1;
- mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mCallback, mContext,
- new Configuration(), taskId, mTaskListener, mDisplayLayout,
- false /* hasShownHint */);
- mHint = (SizeCompatHintPopup)
- LayoutInflater.from(mContext).inflate(R.layout.size_compat_mode_hint, null);
- mHint.inject(mLayout);
-
- spyOn(mLayout);
- }
-
- @Test
- public void testOnClick() {
- doNothing().when(mLayout).dismissHint();
-
- final LinearLayout hintPopup = mHint.findViewById(R.id.size_compat_hint_popup);
- hintPopup.performClick();
-
- verify(mLayout).dismissHint();
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java
deleted file mode 100644
index eb9305b2e995..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.sizecompatui;
-
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.content.res.Configuration;
-import android.graphics.Rect;
-import android.testing.AndroidTestingRunner;
-import android.view.DisplayInfo;
-import android.view.InsetsSource;
-import android.view.InsetsState;
-import android.view.SurfaceControl;
-import android.view.View;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link SizeCompatUILayout}.
- *
- * Build/Install/Run:
- * atest WMShellUnitTests:SizeCompatUILayoutTest
- */
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-public class SizeCompatUILayoutTest extends ShellTestCase {
-
- private static final int TASK_ID = 1;
-
- @Mock private SyncTransactionQueue mSyncTransactionQueue;
- @Mock private SizeCompatUIController.SizeCompatUICallback mCallback;
- @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
- @Mock private DisplayLayout mDisplayLayout;
- @Mock private SizeCompatRestartButton mButton;
- @Mock private SizeCompatHintPopup mHint;
- private Configuration mTaskConfig;
-
- private SizeCompatUILayout mLayout;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mTaskConfig = new Configuration();
-
- mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mCallback, mContext,
- new Configuration(), TASK_ID, mTaskListener, new DisplayLayout(),
- false /* hasShownHint */);
-
- spyOn(mLayout);
- spyOn(mLayout.mButtonWindowManager);
- doReturn(mButton).when(mLayout.mButtonWindowManager).createSizeCompatButton();
-
- final SizeCompatUIWindowManager hintWindowManager = mLayout.createHintWindowManager();
- spyOn(hintWindowManager);
- doReturn(mHint).when(hintWindowManager).createSizeCompatHint();
- doReturn(hintWindowManager).when(mLayout).createHintWindowManager();
- }
-
- @Test
- public void testCreateSizeCompatButton() {
- // Not create button if show is false.
- mLayout.createSizeCompatButton(false /* show */);
-
- verify(mLayout.mButtonWindowManager, never()).createSizeCompatButton();
- assertNull(mLayout.mButton);
- assertNull(mLayout.mHintWindowManager);
- assertNull(mLayout.mHint);
-
- // Not create hint popup.
- mLayout.mShouldShowHint = false;
- mLayout.createSizeCompatButton(true /* show */);
-
- verify(mLayout.mButtonWindowManager).createSizeCompatButton();
- assertNotNull(mLayout.mButton);
- assertNull(mLayout.mHintWindowManager);
- assertNull(mLayout.mHint);
-
- // Create hint popup.
- mLayout.release();
- mLayout.mShouldShowHint = true;
- mLayout.createSizeCompatButton(true /* show */);
-
- verify(mLayout.mButtonWindowManager, times(2)).createSizeCompatButton();
- assertNotNull(mLayout.mButton);
- assertNotNull(mLayout.mHintWindowManager);
- verify(mLayout.mHintWindowManager).createSizeCompatHint();
- assertNotNull(mLayout.mHint);
- assertFalse(mLayout.mShouldShowHint);
- }
-
- @Test
- public void testRelease() {
- mLayout.createSizeCompatButton(true /* show */);
- final SizeCompatUIWindowManager hintWindowManager = mLayout.mHintWindowManager;
-
- mLayout.release();
-
- assertNull(mLayout.mButton);
- assertNull(mLayout.mHint);
- verify(hintWindowManager).release();
- assertNull(mLayout.mHintWindowManager);
- verify(mLayout.mButtonWindowManager).release();
- }
-
- @Test
- public void testUpdateSizeCompatInfo() {
- mLayout.createSizeCompatButton(true /* show */);
-
- // No diff
- clearInvocations(mLayout);
- mLayout.updateSizeCompatInfo(mTaskConfig, mTaskListener, true /* show */);
-
- verify(mLayout, never()).updateButtonSurfacePosition();
- verify(mLayout, never()).release();
- verify(mLayout, never()).createSizeCompatButton(anyBoolean());
-
- // Change task listener, recreate button.
- clearInvocations(mLayout);
- final ShellTaskOrganizer.TaskListener newTaskListener = mock(
- ShellTaskOrganizer.TaskListener.class);
- mLayout.updateSizeCompatInfo(mTaskConfig, newTaskListener,
- true /* show */);
-
- verify(mLayout).release();
- verify(mLayout).createSizeCompatButton(anyBoolean());
-
- // Change task bounds, update position.
- clearInvocations(mLayout);
- final Configuration newTaskConfiguration = new Configuration();
- newTaskConfiguration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000));
- mLayout.updateSizeCompatInfo(newTaskConfiguration, newTaskListener,
- true /* show */);
-
- verify(mLayout).updateButtonSurfacePosition();
- verify(mLayout).updateHintSurfacePosition();
- }
-
- @Test
- public void testUpdateDisplayLayout() {
- final DisplayInfo displayInfo = new DisplayInfo();
- displayInfo.logicalWidth = 1000;
- displayInfo.logicalHeight = 2000;
- final DisplayLayout displayLayout1 = new DisplayLayout(displayInfo,
- mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false);
-
- mLayout.updateDisplayLayout(displayLayout1);
- verify(mLayout).updateButtonSurfacePosition();
- verify(mLayout).updateHintSurfacePosition();
-
- // No update if the display bounds is the same.
- clearInvocations(mLayout);
- final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo,
- mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false);
- mLayout.updateDisplayLayout(displayLayout2);
- verify(mLayout, never()).updateButtonSurfacePosition();
- verify(mLayout, never()).updateHintSurfacePosition();
- }
-
- @Test
- public void testUpdateDisplayLayoutInsets() {
- final DisplayInfo displayInfo = new DisplayInfo();
- displayInfo.logicalWidth = 1000;
- displayInfo.logicalHeight = 2000;
- final DisplayLayout displayLayout = new DisplayLayout(displayInfo,
- mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false);
-
- mLayout.updateDisplayLayout(displayLayout);
- verify(mLayout).updateButtonSurfacePosition();
- verify(mLayout).updateHintSurfacePosition();
-
- // Update if the insets change on the existing display layout
- clearInvocations(mLayout);
- InsetsState insetsState = new InsetsState();
- InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
- insetsSource.setFrame(0, 0, 1000, 1000);
- insetsState.addSource(insetsSource);
- displayLayout.setInsets(mContext.getResources(), insetsState);
- mLayout.updateDisplayLayout(displayLayout);
- verify(mLayout).updateButtonSurfacePosition();
- verify(mLayout).updateHintSurfacePosition();
- }
-
- @Test
- public void testUpdateVisibility() {
- // Create button if it is not created.
- mLayout.mButton = null;
- mLayout.updateVisibility(true /* show */);
-
- verify(mLayout).createSizeCompatButton(true /* show */);
-
- // Hide button.
- clearInvocations(mLayout);
- doReturn(View.VISIBLE).when(mButton).getVisibility();
- mLayout.updateVisibility(false /* show */);
-
- verify(mLayout, never()).createSizeCompatButton(anyBoolean());
- verify(mButton).setVisibility(View.GONE);
-
- // Show button.
- doReturn(View.GONE).when(mButton).getVisibility();
- mLayout.updateVisibility(true /* show */);
-
- verify(mLayout, never()).createSizeCompatButton(anyBoolean());
- verify(mButton).setVisibility(View.VISIBLE);
- }
-
- @Test
- public void testAttachToParentSurface() {
- final SurfaceControl.Builder b = new SurfaceControl.Builder();
- mLayout.attachToParentSurface(b);
-
- verify(mTaskListener).attachChildSurfaceToTask(TASK_ID, b);
- }
-
- @Test
- public void testOnRestartButtonClicked() {
- mLayout.onRestartButtonClicked();
-
- verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID);
- }
-
- @Test
- public void testOnRestartButtonLongClicked_showHint() {
- mLayout.dismissHint();
-
- assertNull(mLayout.mHint);
-
- mLayout.onRestartButtonLongClicked();
-
- assertNotNull(mLayout.mHint);
- }
-
- @Test
- public void testDismissHint() {
- mLayout.onRestartButtonLongClicked();
- final SizeCompatUIWindowManager hintWindowManager = mLayout.mHintWindowManager;
- assertNotNull(mLayout.mHint);
- assertNotNull(hintWindowManager);
-
- mLayout.dismissHint();
-
- assertNull(mLayout.mHint);
- assertNull(mLayout.mHintWindowManager);
- verify(hintWindowManager).release();
- }
-}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 147e73599cad..7f6fb90297b3 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -460,4 +460,8 @@ interface IAudioService {
boolean register);
void setTestDeviceConnectionState(in AudioDeviceAttributes device, boolean connected);
+
+ List<AudioFocusInfo> getFocusStack();
+
+ boolean sendFocusLoss(in AudioFocusInfo focusLoser, in IAudioPolicyCallback apcb);
}
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index 0f08d79bf3bb..3ba1d1f0eca2 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -19,6 +19,7 @@ package android.media.audiopolicy;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
@@ -230,7 +231,7 @@ public class AudioPolicy {
* If set to {@code true}, it is mandatory to set an
* {@link AudioPolicy.AudioPolicyFocusListener} in order to successfully build
* an {@code AudioPolicy} instance.
- * @param enforce true if the policy will govern audio focus decisions.
+ * @param isFocusPolicy true if the policy will govern audio focus decisions.
* @return the same Builder instance.
*/
@NonNull
@@ -723,6 +724,45 @@ public class AudioPolicy {
}
/**
+ * Returns the list of entries in the focus stack.
+ * The list is ordered with increasing rank of focus ownership, where the last entry is at the
+ * top of the focus stack and is the current focus owner.
+ * @return the ordered list of focus owners
+ * @see AudioManager#registerAudioPolicy(AudioPolicy)
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public @NonNull List<AudioFocusInfo> getFocusStack() {
+ try {
+ return getService().getFocusStack();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Send AUDIOFOCUS_LOSS to a specific stack entry, causing it to be notified of the focus
+ * loss, and for it to exit the focus stack (its focus listener will not be invoked after that).
+ * This operation is only valid for a registered policy (with
+ * {@link AudioManager#registerAudioPolicy(AudioPolicy)}) that is also set as the policy focus
+ * listener (with {@link Builder#setAudioPolicyFocusListener(AudioPolicyFocusListener)}.
+ * @param focusLoser the stack entry that is exiting the stack through a focus loss
+ * @return false if the focusLoser wasn't found in the stack, true otherwise
+ * @throws IllegalStateException if used on an unregistered policy, or a registered policy
+ * with no {@link AudioPolicyFocusListener} set
+ * @see AudioManager#registerAudioPolicy(AudioPolicy)
+ * @see Builder#setAudioPolicyStatusListener(AudioPolicyStatusListener)
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public boolean sendFocusLoss(@NonNull AudioFocusInfo focusLoser) throws IllegalStateException {
+ Objects.requireNonNull(focusLoser);
+ try {
+ return getService().sendFocusLoss(focusLoser, cb());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}.
* Audio buffers recorded through the created instance will contain the mix of the audio
* streams that fed the given mixer.
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 94de7fa17ae7..255b391b2259 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -285,7 +285,7 @@ public class Tuner implements AutoCloseable {
@Nullable
private FrontendInfo mFrontendInfo;
private Integer mFrontendHandle;
- private Boolean mIsSharedFrontend = false;
+ private Tuner mFeOwnerTuner = null;
private int mFrontendType = FrontendSettings.TYPE_UNDEFINED;
private int mUserId;
private Lnb mLnb;
@@ -442,11 +442,10 @@ public class Tuner implements AutoCloseable {
mFrontendLock.lock();
try {
mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId);
- synchronized (mIsSharedFrontend) {
- mFrontendHandle = tuner.mFrontendHandle;
- mFrontend = tuner.mFrontend;
- mIsSharedFrontend = true;
- }
+ mFeOwnerTuner = tuner;
+ mFeOwnerTuner.registerFrontendCallbackListener(this);
+ mFrontendHandle = mFeOwnerTuner.mFrontendHandle;
+ mFrontend = mFeOwnerTuner.mFrontend;
nativeShareFrontend(mFrontend.mId);
} finally {
releaseTRMSLock();
@@ -513,6 +512,27 @@ public class Tuner implements AutoCloseable {
private long mNativeContext; // used by native jMediaTuner
/**
+ * Registers a tuner as a listener for frontend callbacks.
+ */
+ private void registerFrontendCallbackListener(Tuner tuner) {
+ nativeRegisterFeCbListener(tuner.getNativeContext());
+ }
+
+ /**
+ * Unregisters a tuner as a listener for frontend callbacks.
+ */
+ private void unregisterFrontendCallbackListener(Tuner tuner) {
+ nativeUnregisterFeCbListener(tuner.getNativeContext());
+ }
+
+ /**
+ * Returns the pointer to the associated JTuner.
+ */
+ long getNativeContext() {
+ return mNativeContext;
+ }
+
+ /**
* Releases the Tuner instance.
*/
@Override
@@ -526,19 +546,21 @@ public class Tuner implements AutoCloseable {
}
}
- private void releaseAll() {
+ private void releaseFrontend() {
mFrontendLock.lock();
try {
if (mFrontendHandle != null) {
- synchronized (mIsSharedFrontend) {
- if (!mIsSharedFrontend) {
- int res = nativeCloseFrontend(mFrontendHandle);
- if (res != Tuner.RESULT_SUCCESS) {
- TunerUtils.throwExceptionForResult(res, "failed to close frontend");
- }
- mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId);
+ if (mFeOwnerTuner != null) {
+ // unregister self from the Frontend callback
+ mFeOwnerTuner.unregisterFrontendCallbackListener(this);
+ mFeOwnerTuner = null;
+ } else {
+ // close resource as owner
+ int res = nativeCloseFrontend(mFrontendHandle);
+ if (res != Tuner.RESULT_SUCCESS) {
+ TunerUtils.throwExceptionForResult(res, "failed to close frontend");
}
- mIsSharedFrontend = false;
+ mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId);
}
FrameworkStatsLog
.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
@@ -549,9 +571,14 @@ public class Tuner implements AutoCloseable {
} finally {
mFrontendLock.unlock();
}
+ }
+
+ private void releaseAll() {
+ releaseFrontend();
mLnbLock.lock();
try {
+ // mLnb will be non-null only for owner tuner
if (mLnb != null) {
mLnb.close();
}
@@ -641,6 +668,8 @@ public class Tuner implements AutoCloseable {
*/
private native Frontend nativeOpenFrontendByHandle(int handle);
private native int nativeShareFrontend(int id);
+ private native void nativeRegisterFeCbListener(long nativeContext);
+ private native void nativeUnregisterFeCbListener(long nativeContext);
@Result
private native int nativeTune(int type, FrontendSettings settings);
private native int nativeStopTune();
@@ -1276,7 +1305,7 @@ public class Tuner implements AutoCloseable {
}
private void onFrontendEvent(int eventType) {
- Log.d(TAG, "Got event from tuning. Event type: " + eventType);
+ Log.d(TAG, "Got event from tuning. Event type: " + eventType + " for " + this);
synchronized (mOnTuneEventLock) {
if (mOnTuneEventExecutor != null && mOnTuneEventListener != null) {
mOnTuneEventExecutor.execute(() -> {
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index ded9652622a7..c230df30accf 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -951,20 +951,45 @@ FilterClientCallbackImpl::~FilterClientCallbackImpl() {
}
/////////////// FrontendClientCallbackImpl ///////////////////////
-FrontendClientCallbackImpl::FrontendClientCallbackImpl(jweak tunerObj) : mObject(tunerObj) {}
+FrontendClientCallbackImpl::FrontendClientCallbackImpl(JTuner* jtuner, jweak listener) {
+ ALOGV("FrontendClientCallbackImpl() with listener:%p", listener);
+ addCallbackListener(jtuner, listener);
+}
+
+void FrontendClientCallbackImpl::addCallbackListener(JTuner* jtuner, jweak listener) {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jweak listenerRef = env->NewWeakGlobalRef(listener);
+ ALOGV("addCallbackListener() with listener:%p and ref:%p @%p", listener, listenerRef, this);
+ std::scoped_lock<std::mutex> lock(mMutex);
+ mListenersMap[jtuner] = listenerRef;
+}
+
+void FrontendClientCallbackImpl::removeCallbackListener(JTuner* listener) {
+ ALOGV("removeCallbackListener for listener:%p", listener);
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ std::scoped_lock<std::mutex> lock(mMutex);
+ if (mListenersMap.find(listener) != mListenersMap.end() && mListenersMap[listener]) {
+ env->DeleteWeakGlobalRef(mListenersMap[listener]);
+ mListenersMap.erase(listener);
+ }
+}
void FrontendClientCallbackImpl::onEvent(FrontendEventType frontendEventType) {
ALOGV("FrontendClientCallbackImpl::onEvent, type=%d", frontendEventType);
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jobject frontend(env->NewLocalRef(mObject));
- if (!env->IsSameObject(frontend, nullptr)) {
- env->CallVoidMethod(
- frontend,
- gFields.onFrontendEventID,
- (jint)frontendEventType);
- } else {
- ALOGE("FrontendClientCallbackImpl::onEvent:"
- "Frontend object has been freed. Ignoring callback.");
+ std::scoped_lock<std::mutex> lock(mMutex);
+ for (const auto& mapEntry : mListenersMap) {
+ ALOGV("JTuner:%p, jweak:%p", mapEntry.first, mapEntry.second);
+ jobject frontend(env->NewLocalRef(mapEntry.second));
+ if (!env->IsSameObject(frontend, nullptr)) {
+ env->CallVoidMethod(
+ frontend,
+ gFields.onFrontendEventID,
+ (jint)frontendEventType);
+ } else {
+ ALOGW("FrontendClientCallbackImpl::onEvent:"
+ "Frontend object has been freed. Ignoring callback.");
+ }
}
}
@@ -973,12 +998,25 @@ void FrontendClientCallbackImpl::onScanMessage(
ALOGV("FrontendClientCallbackImpl::onScanMessage, type=%d", type);
JNIEnv *env = AndroidRuntime::getJNIEnv();
jclass clazz = env->FindClass("android/media/tv/tuner/Tuner");
- jobject frontend(env->NewLocalRef(mObject));
- if (env->IsSameObject(frontend, nullptr)) {
- ALOGE("FrontendClientCallbackImpl::onScanMessage:"
- "Frontend object has been freed. Ignoring callback.");
- return;
+
+ std::scoped_lock<std::mutex> lock(mMutex);
+ for (const auto& mapEntry : mListenersMap) {
+ jobject frontend(env->NewLocalRef(mapEntry.second));
+ if (env->IsSameObject(frontend, nullptr)) {
+ ALOGE("FrontendClientCallbackImpl::onScanMessage:"
+ "Tuner object has been freed. Ignoring callback.");
+ continue;
+ }
+ executeOnScanMessage(env, clazz, frontend, type, message);
}
+}
+
+void FrontendClientCallbackImpl::executeOnScanMessage(
+ JNIEnv *env, const jclass& clazz, const jobject& frontend,
+ FrontendScanMessageType type,
+ const FrontendScanMessage& message) {
+ ALOGV("FrontendClientCallbackImpl::executeOnScanMessage, type=%d", type);
+
switch(type) {
case FrontendScanMessageType::LOCKED: {
if (message.get<FrontendScanMessage::Tag::isLocked>()) {
@@ -1157,11 +1195,14 @@ void FrontendClientCallbackImpl::onScanMessage(
}
FrontendClientCallbackImpl::~FrontendClientCallbackImpl() {
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- if (mObject != nullptr) {
- env->DeleteWeakGlobalRef(mObject);
- mObject = nullptr;
+ JNIEnv *env = android::AndroidRuntime::getJNIEnv();
+ ALOGV("~FrontendClientCallbackImpl()");
+ std::scoped_lock<std::mutex> lock(mMutex);
+ for (const auto& mapEntry : mListenersMap) {
+ ALOGV("deleteRef :%p at @ %p", mapEntry.second, this);
+ env->DeleteWeakGlobalRef(mapEntry.second);
}
+ mListenersMap.clear();
}
/////////////// Tuner ///////////////////////
@@ -1180,6 +1221,10 @@ JTuner::JTuner(JNIEnv *env, jobject thiz) : mClass(nullptr) {
mSharedFeId = (int)Constant::INVALID_FRONTEND_ID;
}
+jweak JTuner::getObject() {
+ return mObject;
+}
+
JTuner::~JTuner() {
if (mFeClient != nullptr) {
mFeClient->close();
@@ -1192,6 +1237,7 @@ JTuner::~JTuner() {
env->DeleteWeakGlobalRef(mObject);
env->DeleteGlobalRef(mClass);
mFeClient = nullptr;
+ mFeClientCb = nullptr;
mDemuxClient = nullptr;
mClass = nullptr;
mObject = nullptr;
@@ -1247,9 +1293,8 @@ jobject JTuner::openFrontendByHandle(int feHandle) {
return nullptr;
}
- sp<FrontendClientCallbackImpl> feClientCb =
- new FrontendClientCallbackImpl(env->NewWeakGlobalRef(mObject));
- mFeClient->setCallback(feClientCb);
+ mFeClientCb = new FrontendClientCallbackImpl(this, mObject);
+ mFeClient->setCallback(mFeClientCb);
// TODO: add more fields to frontend
return env->NewObject(
env->FindClass("android/media/tv/tuner/Tuner$Frontend"),
@@ -1269,6 +1314,18 @@ int JTuner::shareFrontend(int feId) {
return (int)Result::SUCCESS;
}
+void JTuner::registerFeCbListener(JTuner* jtuner) {
+ if (mFeClientCb != nullptr && jtuner != nullptr) {
+ mFeClientCb->addCallbackListener(jtuner, jtuner->getObject());
+ }
+}
+
+void JTuner::unregisterFeCbListener(JTuner* jtuner) {
+ if (mFeClientCb != nullptr && jtuner != nullptr) {
+ mFeClientCb->removeCallbackListener(jtuner);
+ }
+}
+
jobject JTuner::getAnalogFrontendCaps(JNIEnv *env, FrontendCapabilities &caps) {
jclass clazz = env->FindClass("android/media/tv/tuner/frontend/AnalogFrontendCapabilities");
jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V");
@@ -3188,6 +3245,20 @@ static int android_media_tv_Tuner_share_frontend(
return tuner->shareFrontend(id);
}
+static void android_media_tv_Tuner_register_fe_cb_listener(
+ JNIEnv *env, jobject thiz, jlong shareeJTuner) {
+ sp<JTuner> tuner = getTuner(env, thiz);
+ JTuner *jtuner = (JTuner *)shareeJTuner;
+ tuner->registerFeCbListener(jtuner);
+}
+
+static void android_media_tv_Tuner_unregister_fe_cb_listener(
+ JNIEnv *env, jobject thiz, jlong shareeJTuner) {
+ sp<JTuner> tuner = getTuner(env, thiz);
+ JTuner *jtuner = (JTuner *)shareeJTuner;
+ tuner->unregisterFeCbListener(jtuner);
+}
+
static int android_media_tv_Tuner_tune(JNIEnv *env, jobject thiz, jint type, jobject settings) {
sp<JTuner> tuner = getTuner(env, thiz);
FrontendSettings setting = getFrontendSettings(env, type, settings);
@@ -4361,6 +4432,10 @@ static const JNINativeMethod gTunerMethods[] = {
(void *)android_media_tv_Tuner_open_frontend_by_handle },
{ "nativeShareFrontend", "(I)I",
(void *)android_media_tv_Tuner_share_frontend },
+ { "nativeRegisterFeCbListener", "(J)V",
+ (void*)android_media_tv_Tuner_register_fe_cb_listener },
+ { "nativeUnregisterFeCbListener", "(J)V",
+ (void*)android_media_tv_Tuner_unregister_fe_cb_listener },
{ "nativeTune", "(ILandroid/media/tv/tuner/frontend/FrontendSettings;)I",
(void *)android_media_tv_Tuner_tune },
{ "nativeStopTune", "()I", (void *)android_media_tv_Tuner_stop_tune },
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 31d24ee13cf7..06e249212444 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -149,14 +149,21 @@ private:
void getRestartEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
};
+struct JTuner;
struct FrontendClientCallbackImpl : public FrontendClientCallback {
- FrontendClientCallbackImpl(jweak tunerObj);
+ FrontendClientCallbackImpl(JTuner*, jweak);
~FrontendClientCallbackImpl();
virtual void onEvent(FrontendEventType frontendEventType);
virtual void onScanMessage(
FrontendScanMessageType type, const FrontendScanMessage& message);
- jweak mObject;
+ void executeOnScanMessage(JNIEnv *env, const jclass& clazz, const jobject& frontend,
+ FrontendScanMessageType type,
+ const FrontendScanMessage& message);
+ void addCallbackListener(JTuner*, jweak obj);
+ void removeCallbackListener(JTuner* jtuner);
+ std::unordered_map<JTuner*, jweak> mListenersMap;
+ std::mutex mMutex;
};
struct JTuner : public RefBase {
@@ -171,6 +178,8 @@ struct JTuner : public RefBase {
jobject getFrontendIds();
jobject openFrontendByHandle(int feHandle);
int shareFrontend(int feId);
+ void registerFeCbListener(JTuner* jtuner);
+ void unregisterFeCbListener(JTuner* jtuner);
jint closeFrontendById(int id);
jobject getFrontendInfo(int id);
int tune(const FrontendSettings& settings);
@@ -192,6 +201,8 @@ struct JTuner : public RefBase {
jint closeFrontend();
jint closeDemux();
+ jweak getObject();
+
protected:
virtual ~JTuner();
@@ -200,6 +211,7 @@ private:
jweak mObject;
static sp<TunerClient> mTunerClient;
sp<FrontendClient> mFeClient;
+ sp<FrontendClientCallbackImpl> mFeClientCb;
int mFeId;
int mSharedFeId;
sp<LnbClient> mLnbClient;
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index ceba4d60bed0..f76811ee1062 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -364,7 +364,7 @@ void ASurfaceTransaction_setBuffer(ASurfaceTransaction* aSurfaceTransaction,
sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
- sp<GraphicBuffer> graphic_buffer(reinterpret_cast<GraphicBuffer*>(buffer));
+ sp<GraphicBuffer> graphic_buffer(GraphicBuffer::fromAHardwareBuffer(buffer));
std::optional<sp<Fence>> fence = std::nullopt;
if (acquire_fence_fd != -1) {
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 3e65df23fb2b..632dfb397d51 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -27,6 +27,7 @@
<uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
<uses-permission android:name="android.permission.NETWORK_BYPASS_PRIVATE_DNS" />
<uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:label="@string/app_name"
diff --git a/packages/ConnectivityT/framework-t/Android.bp b/packages/ConnectivityT/framework-t/Android.bp
index 9c07f399f92e..3e82b28ce67e 100644
--- a/packages/ConnectivityT/framework-t/Android.bp
+++ b/packages/ConnectivityT/framework-t/Android.bp
@@ -103,12 +103,24 @@ filegroup {
// Connectivity-T common libraries.
filegroup {
+ name: "framework-connectivity-tiramisu-internal-sources",
+ srcs: [
+ "src/android/net/ConnectivityFrameworkInitializerTiramisu.java",
+ ],
+ path: "src",
+ visibility: [
+ "//visibility:private",
+ ],
+}
+
+filegroup {
name: "framework-connectivity-tiramisu-sources",
srcs: [
":framework-connectivity-netstats-sources",
":framework-connectivity-nsd-sources",
+ ":framework-connectivity-tiramisu-internal-sources",
],
visibility: [
"//frameworks/base",
],
-} \ No newline at end of file
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java b/packages/ConnectivityT/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java
new file mode 100644
index 000000000000..630f902ecfd7
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.SystemApi;
+import android.app.SystemServiceRegistry;
+import android.content.Context;
+import android.net.nsd.INsdManager;
+import android.net.nsd.NsdManager;
+
+/**
+ * Class for performing registration for Connectivity services which are exposed via updatable APIs
+ * since Android T.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class ConnectivityFrameworkInitializerTiramisu {
+ private ConnectivityFrameworkInitializerTiramisu() {}
+
+ /**
+ * Called by {@link SystemServiceRegistry}'s static initializer and registers nsd services to
+ * {@link Context}, so that {@link Context#getSystemService} can return them.
+ *
+ * @throws IllegalStateException if this is called anywhere besides
+ * {@link SystemServiceRegistry}.
+ */
+ public static void registerServiceWrappers() {
+ SystemServiceRegistry.registerContextAwareService(
+ Context.NSD_SERVICE,
+ NsdManager.class,
+ (context, serviceBinder) -> {
+ INsdManager service = INsdManager.Stub.asInterface(serviceBinder);
+ return new NsdManager(context, service);
+ }
+ );
+ }
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java
index 6c597e26e042..0f21e55b9f27 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java
@@ -16,10 +16,6 @@
package android.net.nsd;
-import static com.android.internal.util.Preconditions.checkArgument;
-import static com.android.internal.util.Preconditions.checkNotNull;
-import static com.android.internal.util.Preconditions.checkStringNotEmpty;
-
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemService;
@@ -32,11 +28,13 @@ import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Protocol;
+
+import java.util.Objects;
/**
* The Network Service Discovery Manager class provides the API to discover services
@@ -175,65 +173,63 @@ public final class NsdManager {
*/
public static final int NSD_STATE_ENABLED = 2;
- private static final int BASE = Protocol.BASE_NSD_MANAGER;
-
/** @hide */
- public static final int DISCOVER_SERVICES = BASE + 1;
+ public static final int DISCOVER_SERVICES = 1;
/** @hide */
- public static final int DISCOVER_SERVICES_STARTED = BASE + 2;
+ public static final int DISCOVER_SERVICES_STARTED = 2;
/** @hide */
- public static final int DISCOVER_SERVICES_FAILED = BASE + 3;
+ public static final int DISCOVER_SERVICES_FAILED = 3;
/** @hide */
- public static final int SERVICE_FOUND = BASE + 4;
+ public static final int SERVICE_FOUND = 4;
/** @hide */
- public static final int SERVICE_LOST = BASE + 5;
+ public static final int SERVICE_LOST = 5;
/** @hide */
- public static final int STOP_DISCOVERY = BASE + 6;
+ public static final int STOP_DISCOVERY = 6;
/** @hide */
- public static final int STOP_DISCOVERY_FAILED = BASE + 7;
+ public static final int STOP_DISCOVERY_FAILED = 7;
/** @hide */
- public static final int STOP_DISCOVERY_SUCCEEDED = BASE + 8;
+ public static final int STOP_DISCOVERY_SUCCEEDED = 8;
/** @hide */
- public static final int REGISTER_SERVICE = BASE + 9;
+ public static final int REGISTER_SERVICE = 9;
/** @hide */
- public static final int REGISTER_SERVICE_FAILED = BASE + 10;
+ public static final int REGISTER_SERVICE_FAILED = 10;
/** @hide */
- public static final int REGISTER_SERVICE_SUCCEEDED = BASE + 11;
+ public static final int REGISTER_SERVICE_SUCCEEDED = 11;
/** @hide */
- public static final int UNREGISTER_SERVICE = BASE + 12;
+ public static final int UNREGISTER_SERVICE = 12;
/** @hide */
- public static final int UNREGISTER_SERVICE_FAILED = BASE + 13;
+ public static final int UNREGISTER_SERVICE_FAILED = 13;
/** @hide */
- public static final int UNREGISTER_SERVICE_SUCCEEDED = BASE + 14;
+ public static final int UNREGISTER_SERVICE_SUCCEEDED = 14;
/** @hide */
- public static final int RESOLVE_SERVICE = BASE + 18;
+ public static final int RESOLVE_SERVICE = 15;
/** @hide */
- public static final int RESOLVE_SERVICE_FAILED = BASE + 19;
+ public static final int RESOLVE_SERVICE_FAILED = 16;
/** @hide */
- public static final int RESOLVE_SERVICE_SUCCEEDED = BASE + 20;
+ public static final int RESOLVE_SERVICE_SUCCEEDED = 17;
/** @hide */
- public static final int DAEMON_CLEANUP = BASE + 21;
+ public static final int DAEMON_CLEANUP = 18;
/** @hide */
- public static final int DAEMON_STARTUP = BASE + 22;
+ public static final int DAEMON_STARTUP = 19;
/** @hide */
- public static final int ENABLE = BASE + 24;
+ public static final int ENABLE = 20;
/** @hide */
- public static final int DISABLE = BASE + 25;
+ public static final int DISABLE = 21;
/** @hide */
- public static final int NATIVE_DAEMON_EVENT = BASE + 26;
+ public static final int NATIVE_DAEMON_EVENT = 22;
/** @hide */
- public static final int REGISTER_CLIENT = BASE + 27;
+ public static final int REGISTER_CLIENT = 23;
/** @hide */
- public static final int UNREGISTER_CLIENT = BASE + 28;
+ public static final int UNREGISTER_CLIENT = 24;
/** Dns based service discovery protocol */
public static final int PROTOCOL_DNS_SD = 0x0001;
@@ -550,7 +546,9 @@ public final class NsdManager {
final int key;
synchronized (mMapLock) {
int valueIndex = mListenerMap.indexOfValue(listener);
- checkArgument(valueIndex == -1, "listener already in use");
+ if (valueIndex != -1) {
+ throw new IllegalArgumentException("listener already in use");
+ }
key = nextListenerKey();
mListenerMap.put(key, listener);
mServiceMap.put(key, s);
@@ -569,7 +567,9 @@ public final class NsdManager {
checkListener(listener);
synchronized (mMapLock) {
int valueIndex = mListenerMap.indexOfValue(listener);
- checkArgument(valueIndex != -1, "listener not registered");
+ if (valueIndex == -1) {
+ throw new IllegalArgumentException("listener not registered");
+ }
return mListenerMap.keyAt(valueIndex);
}
}
@@ -598,7 +598,9 @@ public final class NsdManager {
*/
public void registerService(NsdServiceInfo serviceInfo, int protocolType,
RegistrationListener listener) {
- checkArgument(serviceInfo.getPort() > 0, "Invalid port number");
+ if (serviceInfo.getPort() <= 0) {
+ throw new IllegalArgumentException("Invalid port number");
+ }
checkServiceInfo(serviceInfo);
checkProtocol(protocolType);
int key = putListener(listener, serviceInfo);
@@ -660,7 +662,9 @@ public final class NsdManager {
* Cannot be null. Cannot be in use for an active service discovery.
*/
public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
- checkStringNotEmpty(serviceType, "Service type cannot be empty");
+ if (TextUtils.isEmpty(serviceType)) {
+ throw new IllegalArgumentException("Service type cannot be empty");
+ }
checkProtocol(protocolType);
NsdServiceInfo s = new NsdServiceInfo();
@@ -719,16 +723,22 @@ public final class NsdManager {
}
private static void checkListener(Object listener) {
- checkNotNull(listener, "listener cannot be null");
+ Objects.requireNonNull(listener, "listener cannot be null");
}
private static void checkProtocol(int protocolType) {
- checkArgument(protocolType == PROTOCOL_DNS_SD, "Unsupported protocol");
+ if (protocolType != PROTOCOL_DNS_SD) {
+ throw new IllegalArgumentException("Unsupported protocol");
+ }
}
private static void checkServiceInfo(NsdServiceInfo serviceInfo) {
- checkNotNull(serviceInfo, "NsdServiceInfo cannot be null");
- checkStringNotEmpty(serviceInfo.getServiceName(), "Service name cannot be empty");
- checkStringNotEmpty(serviceInfo.getServiceType(), "Service type cannot be empty");
+ Objects.requireNonNull(serviceInfo, "NsdServiceInfo cannot be null");
+ if (TextUtils.isEmpty(serviceInfo.getServiceName())) {
+ throw new IllegalArgumentException("Service name cannot be empty");
+ }
+ if (TextUtils.isEmpty(serviceInfo.getServiceType())) {
+ throw new IllegalArgumentException("Service type cannot be empty");
+ }
}
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index a1b3df87f8d4..1e9a41e4d0b8 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -218,7 +218,6 @@
<!-- notifications & DND access -->
<uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS" />
- <uses-permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS" />
<uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
diff --git a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
index 67a70bb39964..b3987f1aeeda 100644
--- a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
+++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
@@ -94,4 +94,9 @@
android:fromId="@id/unlocked"
android:toId="@id/unlocked_aod"
android:drawable="@drawable/unlocked_ls_to_aod" />
+
+ <transition
+ android:fromId="@id/unlocked"
+ android:toId="@id/locked_aod"
+ android:drawable="@drawable/unlocked_to_aod_lock" />
</animated-selector>
diff --git a/packages/SystemUI/res-keyguard/drawable/unlocked_to_aod_lock.xml b/packages/SystemUI/res-keyguard/drawable/unlocked_to_aod_lock.xml
new file mode 100644
index 000000000000..b6d76e01ad95
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/unlocked_to_aod_lock.xml
@@ -0,0 +1,169 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<animated-vector xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
+ <aapt:attr name="android:drawable">
+ <vector android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_2_G_T_1" android:translateX="22.75" android:translateY="22.25" android:scaleX="1.02" android:scaleY="1.02">
+ <group android:name="_R_G_L_2_G" android:translateX="-8.75" android:translateY="-8.75">
+ <path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#FF000000"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2.5"
+ android:strokeAlpha="1"
+ android:pathData=" M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 " />
+ </group>
+ </group>
+ <group android:name="_R_G_L_1_G" android:translateX="23" android:translateY="32.125">
+ <path android:name="_R_G_L_1_G_D_0_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2"
+ android:strokeAlpha="1"
+ android:pathData=" M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c " />
+ </group>
+ <group android:name="_R_G_L_0_G" android:translateX="23" android:translateY="32.125">
+ <path android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillColor="#FF000000"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_2_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="strokeWidth"
+ android:duration="333" android:startOffset="0" android:valueFrom="2.5" android:valueTo="2" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="83" android:startOffset="0" android:valueFrom="M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 " android:valueTo="M27.13 10.19 C27.13,10.19 27.13,3.67 27.13,3.67 C27.13,0.3 24.38,-1.75 21.13,-1.87 C17.68,-2.01 14.94,0.11 14.94,3.49 C14.94,3.49 15,15 15,15 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.456,0 0.464,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData"
+ android:duration="133" android:startOffset="83" android:valueFrom="M27.13 10.19 C27.13,10.19 27.13,3.67 27.13,3.67 C27.13,0.3 24.38,-1.75 21.13,-1.87 C17.68,-2.01 14.94,0.11 14.94,3.49 C14.94,3.49 15,15 15,15 " android:valueTo="M2.5 10.38 C2.5,10.38 2.5,3.99 2.5,3.99 C2.5,0.61 5.3,-2.12 8.75,-2.12 C12.2,-2.12 15,0.61 15,3.99 C15,3.99 15,15 15,15 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.606,0 0.035,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData"
+ android:duration="117" android:startOffset="217" android:valueFrom="M2.5 10.38 C2.5,10.38 2.5,3.99 2.5,3.99 C2.5,0.61 5.3,-2.12 8.75,-2.12 C12.2,-2.12 15,0.61 15,3.99 C15,3.99 15,15 15,15 " android:valueTo="M2.5 15 C2.5,15 2.5,8.61 2.5,8.61 C2.5,5.24 5.3,2.5 8.75,2.5 C12.2,2.5 15,5.24 15,8.61 C15,8.61 15,15 15,15 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.511,0 0.409,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="translateX"
+ android:duration="333" android:startOffset="0" android:valueFrom="22.75" android:valueTo="23" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="translateY" android:duration="333" android:startOffset="0" android:valueFrom="22.25" android:valueTo="25.5" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="scaleX"
+ android:duration="333" android:startOffset="0" android:valueFrom="1.02" android:valueTo="0.72" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY"
+ android:duration="333" android:startOffset="0" android:valueFrom="1.02" android:valueTo="0.72" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="strokeWidth"
+ android:duration="333" android:startOffset="0" android:valueFrom="2" android:valueTo="1.5" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.38,0 0.274,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="333" android:startOffset="0" android:valueFrom="M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c " android:valueTo="M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.431,0 0.133,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="333" android:startOffset="0" android:valueFrom="M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c " android:valueTo="M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.431,0 0.133,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="translateX"
+ android:duration="850" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
index a2b8bf6d3d4c..4f0925f3bfbb 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
@@ -20,19 +20,18 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/keyguard_bouncer_user_switcher"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
android:gravity="center"
- android:paddingTop="12dp"
android:importantForAccessibility="yes"> <!-- Needed because TYPE_WINDOW_STATE_CHANGED is sent
from this view when bouncer is shown -->
- <ImageView
- android:id="@+id/user_icon"
- android:layout_width="@dimen/keyguard_user_switcher_icon_size"
- android:layout_height="@dimen/keyguard_user_switcher_icon_size" />
+ <ImageView
+ android:id="@+id/user_icon"
+ android:layout_width="@dimen/keyguard_user_switcher_icon_size"
+ android:layout_height="@dimen/keyguard_user_switcher_icon_size" />
<!-- need to keep this outer view in order to have a correctly sized anchor
for the dropdown menu, as well as dropdown background in the right place -->
@@ -41,7 +40,7 @@
android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
- android:layout_marginTop="32dp"
+ android:layout_marginTop="30dp"
android:minHeight="48dp">
<TextView
style="@style/Keyguard.UserSwitcher.Spinner.Header"
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 95330400145f..2819dc9c27b7 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -108,10 +108,10 @@
<dimen name="one_handed_bouncer_move_animation_translation">120dp</dimen>
- <dimen name="keyguard_user_switcher_header_text_size">24sp</dimen>
- <dimen name="keyguard_user_switcher_item_text_size">18sp</dimen>
- <dimen name="keyguard_user_switcher_width">300dp</dimen>
- <dimen name="keyguard_user_switcher_icon_size">250dp</dimen>
+ <dimen name="keyguard_user_switcher_header_text_size">32sp</dimen>
+ <dimen name="keyguard_user_switcher_item_text_size">32sp</dimen>
+ <dimen name="keyguard_user_switcher_width">320dp</dimen>
+ <dimen name="keyguard_user_switcher_icon_size">310dp</dimen>
<dimen name="keyguard_user_switcher_corner">32dp</dimen>
<dimen name="keyguard_user_switcher_popup_corner">24dp</dimen>
<dimen name="keyguard_user_switcher_item_padding_vertical">15dp</dimen>
diff --git a/packages/SystemUI/res/drawable/ic_signal_wifi_off.xml b/packages/SystemUI/res/drawable/ic_signal_wifi_off.xml
new file mode 100644
index 000000000000..a93abb71c9d7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_signal_wifi_off.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18.575,15.2L7.55,4.175q1.1,-0.325 2.212,-0.462 1.113,-0.138 2.238,-0.138 3.45,0 6.55,1.45Q21.65,6.475 24,9zM20.225,23.575l-4.75,-4.75 -3.475,4.1L0,9q0.675,-0.725 1.438,-1.4 0.762,-0.675 1.612,-1.225l-2.625,-2.6L2.1,2.1l19.8,19.8z"/>
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml
new file mode 100644
index 000000000000..b611ffa6d84e
--- /dev/null
+++ b/packages/SystemUI/res/layout/dream_overlay_container.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+-->
+<com.android.systemui.dreams.DreamOverlayContainerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/dream_overlay_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/dream_overlay_content"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent" />
+
+ <com.android.systemui.dreams.DreamOverlayStatusBarView
+ android:id="@+id/dream_overlay_status_bar"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/dream_overlay_status_bar_height"
+ android:layout_marginEnd="@dimen/dream_overlay_status_bar_margin"
+ android:layout_marginStart="@dimen/dream_overlay_status_bar_margin"
+ app:layout_constraintTop_toTopOf="parent">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/dream_overlay_system_status"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <com.android.systemui.statusbar.AlphaOptimizedImageView
+ android:id="@+id/dream_overlay_wifi_status"
+ android:layout_width="@dimen/status_bar_wifi_signal_size"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+ android:visibility="gone"
+ app:layout_constraintEnd_toStartOf="@id/dream_overlay_battery" />
+
+ <com.android.systemui.battery.BatteryMeterView
+ android:id="@+id/dream_overlay_battery"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+ </com.android.systemui.dreams.DreamOverlayStatusBarView>
+</com.android.systemui.dreams.DreamOverlayContainerView> \ No newline at end of file
diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml
index f057603e2cd0..1d6f279afc66 100644
--- a/packages/SystemUI/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/res/values-h800dp/dimens.xml
@@ -22,5 +22,5 @@
<dimen name="large_clock_text_size">200dp</dimen>
<!-- With the large clock, move up slightly from the center -->
- <dimen name="keyguard_large_clock_top_margin">-104dp</dimen>
+ <dimen name="keyguard_large_clock_top_margin">-112dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c7350a1eee8b..d4398a8d35a2 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -30,10 +30,10 @@
<dimen name="navigation_bar_deadzone_size_max">32dp</dimen>
<!-- dimensions for the navigation bar handle -->
- <dimen name="navigation_handle_radius">1dp</dimen>
- <dimen name="navigation_handle_bottom">6dp</dimen>
+ <dimen name="navigation_handle_radius">2dp</dimen>
+ <dimen name="navigation_handle_bottom">10dp</dimen>
<dimen name="navigation_handle_sample_horizontal_margin">10dp</dimen>
- <dimen name="navigation_home_handle_width">72dp</dimen>
+ <dimen name="navigation_home_handle_width">108dp</dimen>
<!-- Size of the nav bar edge panels, should be greater to the
edge sensitivity + the drag threshold -->
@@ -602,7 +602,7 @@
<!-- When large clock is showing, offset the smartspace by this amount -->
<dimen name="keyguard_smartspace_top_offset">12dp</dimen>
<!-- With the large clock, move up slightly from the center -->
- <dimen name="keyguard_large_clock_top_margin">-52dp</dimen>
+ <dimen name="keyguard_large_clock_top_margin">-60dp</dimen>
<!-- Default line spacing multiplier between hours and minutes of the keyguard clock -->
<item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
@@ -1303,4 +1303,9 @@
<dimen name="keyguard_unfold_translation_x">16dp</dimen>
<dimen name="fgs_manager_min_width_minor">100%</dimen>
+
+ <!-- Dream overlay related dimensions -->
+ <dimen name="dream_overlay_status_bar_height">80dp</dimen>
+ <dimen name="dream_overlay_status_bar_margin">40dp</dimen>
+ <dimen name="dream_overlay_status_icon_margin">8dp</dimen>
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 30db13611f4a..c1d9d0d0e142 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -65,6 +65,7 @@ public class RemoteAnimationTargetCompat {
public final Rect localBounds;
public final Rect sourceContainerBounds;
public final Rect screenSpaceBounds;
+ public final Rect startScreenSpaceBounds;
public final boolean isNotInRecents;
public final Rect contentInsets;
public final ActivityManager.RunningTaskInfo taskInfo;
@@ -88,6 +89,7 @@ public class RemoteAnimationTargetCompat {
localBounds = app.localBounds;
sourceContainerBounds = app.sourceContainerBounds;
screenSpaceBounds = app.screenSpaceBounds;
+ startScreenSpaceBounds = screenSpaceBounds;
prefixOrderIndex = app.prefixOrderIndex;
isNotInRecents = app.isNotInRecents;
contentInsets = app.contentInsets;
@@ -219,6 +221,8 @@ public class RemoteAnimationTargetCompat {
localBounds.offsetTo(change.getEndRelOffset().x, change.getEndRelOffset().y);
sourceContainerBounds = null;
screenSpaceBounds = new Rect(change.getEndAbsBounds());
+ startScreenSpaceBounds = new Rect(change.getStartAbsBounds());
+
prefixOrderIndex = order;
// TODO(shell-transitions): I guess we need to send content insets? evaluate how its used.
contentInsets = new Rect(0, 0, 0, 0);
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index ac463ebc1c97..157191302010 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -168,6 +168,12 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
if (!mIsDozing) mView.animateAppearOnLockscreen();
}
+ /** Animate the clock appearance when a foldable device goes from fully-open/half-open state to
+ * fully folded state and it goes to sleep (always on display screen) */
+ public void animateFoldAppear() {
+ mView.animateFoldAppear();
+ }
+
/**
* Updates the time for the view.
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
deleted file mode 100644
index 2a0c2855c3b2..000000000000
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright (C) 2020 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.keyguard;
-
-import android.annotation.FloatRange;
-import android.annotation.IntRange;
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.text.format.DateFormat;
-import android.util.AttributeSet;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-
-import java.util.Calendar;
-import java.util.Locale;
-import java.util.TimeZone;
-
-import kotlin.Unit;
-
-/**
- * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
- * The time's text color is a gradient that changes its colors based on its controller.
- */
-public class AnimatableClockView extends TextView {
- private static final CharSequence DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm";
- private static final CharSequence DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm";
- private static final long DOZE_ANIM_DURATION = 300;
- private static final long APPEAR_ANIM_DURATION = 350;
- private static final long CHARGE_ANIM_DURATION_PHASE_0 = 500;
- private static final long CHARGE_ANIM_DURATION_PHASE_1 = 1000;
-
- private final Calendar mTime = Calendar.getInstance();
-
- private final int mDozingWeight;
- private final int mLockScreenWeight;
- private CharSequence mFormat;
- private CharSequence mDescFormat;
- private int mDozingColor;
- private int mLockScreenColor;
- private float mLineSpacingScale = 1f;
- private int mChargeAnimationDelay = 0;
-
- private TextAnimator mTextAnimator = null;
- private Runnable mOnTextAnimatorInitialized;
-
- private boolean mIsSingleLine;
-
- public AnimatableClockView(Context context) {
- this(context, null, 0, 0);
- }
-
- public AnimatableClockView(Context context, AttributeSet attrs) {
- this(context, attrs, 0, 0);
- }
-
- public AnimatableClockView(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public AnimatableClockView(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray ta = context.obtainStyledAttributes(
- attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes);
- try {
- mDozingWeight = ta.getInt(R.styleable.AnimatableClockView_dozeWeight, 100);
- mLockScreenWeight = ta.getInt(R.styleable.AnimatableClockView_lockScreenWeight, 300);
- mChargeAnimationDelay = ta.getInt(
- R.styleable.AnimatableClockView_chargeAnimationDelay, 200);
- } finally {
- ta.recycle();
- }
-
- ta = context.obtainStyledAttributes(
- attrs, android.R.styleable.TextView, defStyleAttr, defStyleRes);
- try {
- mIsSingleLine = ta.getBoolean(android.R.styleable.TextView_singleLine, false);
- } finally {
- ta.recycle();
- }
-
- refreshFormat();
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
- refreshFormat();
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- }
-
- int getDozingWeight() {
- if (useBoldedVersion()) {
- return mDozingWeight + 100;
- }
- return mDozingWeight;
- }
-
- int getLockScreenWeight() {
- if (useBoldedVersion()) {
- return mLockScreenWeight + 100;
- }
- return mLockScreenWeight;
- }
-
- /**
- * Whether to use a bolded version based on the user specified fontWeightAdjustment.
- */
- boolean useBoldedVersion() {
- // "Bold text" fontWeightAdjustment is 300.
- return getResources().getConfiguration().fontWeightAdjustment > 100;
- }
-
- void refreshTime() {
- mTime.setTimeInMillis(System.currentTimeMillis());
- setText(DateFormat.format(mFormat, mTime));
- setContentDescription(DateFormat.format(mDescFormat, mTime));
- }
-
- void onTimeZoneChanged(TimeZone timeZone) {
- mTime.setTimeZone(timeZone);
- refreshFormat();
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mTextAnimator == null) {
- mTextAnimator = new TextAnimator(
- getLayout(),
- () -> {
- invalidate();
- return Unit.INSTANCE;
- });
- if (mOnTextAnimatorInitialized != null) {
- mOnTextAnimatorInitialized.run();
- mOnTextAnimatorInitialized = null;
- }
- } else {
- mTextAnimator.updateLayout(getLayout());
- }
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- mTextAnimator.draw(canvas);
- }
-
- void setLineSpacingScale(float scale) {
- mLineSpacingScale = scale;
- setLineSpacing(0, mLineSpacingScale);
- }
-
- void setColors(int dozingColor, int lockScreenColor) {
- mDozingColor = dozingColor;
- mLockScreenColor = lockScreenColor;
- }
-
- void animateAppearOnLockscreen() {
- if (mTextAnimator == null) {
- return;
- }
-
- setTextStyle(
- getDozingWeight(),
- -1 /* text size, no update */,
- mLockScreenColor,
- false /* animate */,
- 0 /* duration */,
- 0 /* delay */,
- null /* onAnimationEnd */);
-
- setTextStyle(
- getLockScreenWeight(),
- -1 /* text size, no update */,
- mLockScreenColor,
- true, /* animate */
- APPEAR_ANIM_DURATION,
- 0 /* delay */,
- null /* onAnimationEnd */);
- }
-
- void animateCharge(DozeStateGetter dozeStateGetter) {
- if (mTextAnimator == null || mTextAnimator.isRunning()) {
- // Skip charge animation if dozing animation is already playing.
- return;
- }
- Runnable startAnimPhase2 = () -> setTextStyle(
- dozeStateGetter.isDozing() ? getDozingWeight() : getLockScreenWeight() /* weight */,
- -1,
- null,
- true /* animate */,
- CHARGE_ANIM_DURATION_PHASE_1,
- 0 /* delay */,
- null /* onAnimationEnd */);
- setTextStyle(dozeStateGetter.isDozing()
- ? getLockScreenWeight()
- : getDozingWeight()/* weight */,
- -1,
- null,
- true /* animate */,
- CHARGE_ANIM_DURATION_PHASE_0,
- mChargeAnimationDelay,
- startAnimPhase2);
- }
-
- void animateDoze(boolean isDozing, boolean animate) {
- setTextStyle(isDozing ? getDozingWeight() : getLockScreenWeight() /* weight */,
- -1,
- isDozing ? mDozingColor : mLockScreenColor,
- animate,
- DOZE_ANIM_DURATION,
- 0 /* delay */,
- null /* onAnimationEnd */);
- }
-
- /**
- * Set text style with an optional animation.
- *
- * By passing -1 to weight, the view preserves its current weight.
- * By passing -1 to textSize, the view preserves its current text size.
- *
- * @param weight text weight.
- * @param textSize font size.
- * @param animate true to animate the text style change, otherwise false.
- */
- private void setTextStyle(
- @IntRange(from = 0, to = 1000) int weight,
- @FloatRange(from = 0) float textSize,
- Integer color,
- boolean animate,
- long duration,
- long delay,
- Runnable onAnimationEnd) {
- if (mTextAnimator != null) {
- mTextAnimator.setTextStyle(weight, textSize, color, animate, duration, null,
- delay, onAnimationEnd);
- } else {
- // when the text animator is set, update its start values
- mOnTextAnimatorInitialized =
- () -> mTextAnimator.setTextStyle(
- weight, textSize, color, false, duration, null,
- delay, onAnimationEnd);
- }
- }
-
- void refreshFormat() {
- Patterns.update(mContext);
-
- final boolean use24HourFormat = DateFormat.is24HourFormat(getContext());
- if (mIsSingleLine && use24HourFormat) {
- mFormat = Patterns.sClockView24;
- } else if (!mIsSingleLine && use24HourFormat) {
- mFormat = DOUBLE_LINE_FORMAT_24_HOUR;
- } else if (mIsSingleLine && !use24HourFormat) {
- mFormat = Patterns.sClockView12;
- } else {
- mFormat = DOUBLE_LINE_FORMAT_12_HOUR;
- }
-
- mDescFormat = use24HourFormat ? Patterns.sClockView24 : Patterns.sClockView12;
- refreshTime();
- }
-
- // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
- // This is an optimization to ensure we only recompute the patterns when the inputs change.
- private static final class Patterns {
- static String sClockView12;
- static String sClockView24;
- static String sCacheKey;
-
- static void update(Context context) {
- final Locale locale = Locale.getDefault();
- final Resources res = context.getResources();
- final String clockView12Skel = res.getString(R.string.clock_12hr_format);
- final String clockView24Skel = res.getString(R.string.clock_24hr_format);
- final String key = locale.toString() + clockView12Skel + clockView24Skel;
- if (key.equals(sCacheKey)) return;
- sClockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel);
-
- // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
- // format. The following code removes the AM/PM indicator if we didn't want it.
- if (!clockView12Skel.contains("a")) {
- sClockView12 = sClockView12.replaceAll("a", "").trim();
- }
- sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel);
- sCacheKey = key;
- }
- }
-
- interface DozeStateGetter {
- boolean isDozing();
- }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt
new file mode 100644
index 000000000000..357be2503a3a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard
+
+import android.animation.TimeInterpolator
+import android.annotation.ColorInt
+import android.annotation.FloatRange
+import android.annotation.IntRange
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Canvas
+import android.text.format.DateFormat
+import android.util.AttributeSet
+import android.widget.TextView
+import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator
+import java.util.Calendar
+import java.util.Locale
+import java.util.TimeZone
+
+/**
+ * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
+ * The time's text color is a gradient that changes its colors based on its controller.
+ */
+class AnimatableClockView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : TextView(context, attrs, defStyleAttr, defStyleRes) {
+
+ private val time = Calendar.getInstance()
+
+ private val dozingWeightInternal: Int
+ private val lockScreenWeightInternal: Int
+ private val isSingleLineInternal: Boolean
+
+ private var format: CharSequence? = null
+ private var descFormat: CharSequence? = null
+
+ @ColorInt
+ private var dozingColor = 0
+
+ @ColorInt
+ private var lockScreenColor = 0
+
+ private var lineSpacingScale = 1f
+ private val chargeAnimationDelay: Int
+ private var textAnimator: TextAnimator? = null
+ private var onTextAnimatorInitialized: Runnable? = null
+
+ val dozingWeight: Int
+ get() = if (useBoldedVersion()) dozingWeightInternal + 100 else dozingWeightInternal
+
+ val lockScreenWeight: Int
+ get() = if (useBoldedVersion()) lockScreenWeightInternal + 100 else lockScreenWeightInternal
+
+ init {
+ val animatableClockViewAttributes = context.obtainStyledAttributes(
+ attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes
+ )
+
+ try {
+ dozingWeightInternal = animatableClockViewAttributes.getInt(
+ R.styleable.AnimatableClockView_dozeWeight,
+ 100
+ )
+ lockScreenWeightInternal = animatableClockViewAttributes.getInt(
+ R.styleable.AnimatableClockView_lockScreenWeight,
+ 300
+ )
+ chargeAnimationDelay = animatableClockViewAttributes.getInt(
+ R.styleable.AnimatableClockView_chargeAnimationDelay, 200
+ )
+ } finally {
+ animatableClockViewAttributes.recycle()
+ }
+
+ val textViewAttributes = context.obtainStyledAttributes(
+ attrs, android.R.styleable.TextView,
+ defStyleAttr, defStyleRes
+ )
+
+ isSingleLineInternal =
+ try {
+ textViewAttributes.getBoolean(android.R.styleable.TextView_singleLine, false)
+ } finally {
+ textViewAttributes.recycle()
+ }
+
+ refreshFormat()
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ refreshFormat()
+ }
+
+ /**
+ * Whether to use a bolded version based on the user specified fontWeightAdjustment.
+ */
+ fun useBoldedVersion(): Boolean {
+ // "Bold text" fontWeightAdjustment is 300.
+ return resources.configuration.fontWeightAdjustment > 100
+ }
+
+ fun refreshTime() {
+ time.timeInMillis = System.currentTimeMillis()
+ text = DateFormat.format(format, time)
+ contentDescription = DateFormat.format(descFormat, time)
+ }
+
+ fun onTimeZoneChanged(timeZone: TimeZone?) {
+ time.timeZone = timeZone
+ refreshFormat()
+ }
+
+ @SuppressLint("DrawAllocation")
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ val animator = textAnimator
+ if (animator == null) {
+ textAnimator = TextAnimator(layout) { invalidate() }
+ onTextAnimatorInitialized?.run()
+ onTextAnimatorInitialized = null
+ } else {
+ animator.updateLayout(layout)
+ }
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ textAnimator?.draw(canvas)
+ }
+
+ fun setLineSpacingScale(scale: Float) {
+ lineSpacingScale = scale
+ setLineSpacing(0f, lineSpacingScale)
+ }
+
+ fun setColors(@ColorInt dozingColor: Int, lockScreenColor: Int) {
+ this.dozingColor = dozingColor
+ this.lockScreenColor = lockScreenColor
+ }
+
+ fun animateAppearOnLockscreen() {
+ if (textAnimator == null) {
+ return
+ }
+ setTextStyle(
+ weight = dozingWeight,
+ textSize = -1f,
+ color = lockScreenColor,
+ animate = false,
+ duration = 0,
+ delay = 0,
+ onAnimationEnd = null
+ )
+ setTextStyle(
+ weight = lockScreenWeight,
+ textSize = -1f,
+ color = lockScreenColor,
+ animate = true,
+ duration = APPEAR_ANIM_DURATION,
+ delay = 0,
+ onAnimationEnd = null
+ )
+ }
+
+ fun animateFoldAppear() {
+ if (textAnimator == null) {
+ return
+ }
+ setTextStyle(
+ weight = lockScreenWeightInternal,
+ textSize = -1f,
+ color = lockScreenColor,
+ animate = false,
+ duration = 0,
+ delay = 0,
+ onAnimationEnd = null
+ )
+ setTextStyle(
+ weight = dozingWeightInternal,
+ textSize = -1f,
+ color = dozingColor,
+ animate = true,
+ interpolator = Interpolators.EMPHASIZED_DECELERATE,
+ duration = StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD.toLong(),
+ delay = 0,
+ onAnimationEnd = null
+ )
+ }
+
+ fun animateCharge(dozeStateGetter: DozeStateGetter) {
+ if (textAnimator == null || textAnimator!!.isRunning()) {
+ // Skip charge animation if dozing animation is already playing.
+ return
+ }
+ val startAnimPhase2 = Runnable {
+ setTextStyle(
+ weight = if (dozeStateGetter.isDozing) dozingWeight else lockScreenWeight,
+ textSize = -1f,
+ color = null,
+ animate = true,
+ duration = CHARGE_ANIM_DURATION_PHASE_1,
+ delay = 0,
+ onAnimationEnd = null
+ )
+ }
+ setTextStyle(
+ weight = if (dozeStateGetter.isDozing) lockScreenWeight else dozingWeight,
+ textSize = -1f,
+ color = null,
+ animate = true,
+ duration = CHARGE_ANIM_DURATION_PHASE_0,
+ delay = chargeAnimationDelay.toLong(),
+ onAnimationEnd = startAnimPhase2
+ )
+ }
+
+ fun animateDoze(isDozing: Boolean, animate: Boolean) {
+ setTextStyle(
+ weight = if (isDozing) dozingWeight else lockScreenWeight,
+ textSize = -1f,
+ color = if (isDozing) dozingColor else lockScreenColor,
+ animate = animate,
+ duration = DOZE_ANIM_DURATION,
+ delay = 0,
+ onAnimationEnd = null
+ )
+ }
+
+ /**
+ * Set text style with an optional animation.
+ *
+ * By passing -1 to weight, the view preserves its current weight.
+ * By passing -1 to textSize, the view preserves its current text size.
+ *
+ * @param weight text weight.
+ * @param textSize font size.
+ * @param animate true to animate the text style change, otherwise false.
+ */
+ private fun setTextStyle(
+ @IntRange(from = 0, to = 1000) weight: Int,
+ @FloatRange(from = 0.0) textSize: Float,
+ color: Int?,
+ animate: Boolean,
+ interpolator: TimeInterpolator?,
+ duration: Long,
+ delay: Long,
+ onAnimationEnd: Runnable?
+ ) {
+ if (textAnimator != null) {
+ textAnimator?.setTextStyle(
+ weight = weight,
+ textSize = textSize,
+ color = color,
+ animate = animate,
+ duration = duration,
+ interpolator = interpolator,
+ delay = delay,
+ onAnimationEnd = onAnimationEnd
+ )
+ } else {
+ // when the text animator is set, update its start values
+ onTextAnimatorInitialized = Runnable {
+ textAnimator?.setTextStyle(
+ weight = weight,
+ textSize = textSize,
+ color = color,
+ animate = false,
+ duration = duration,
+ interpolator = interpolator,
+ delay = delay,
+ onAnimationEnd = onAnimationEnd
+ )
+ }
+ }
+ }
+
+ private fun setTextStyle(
+ @IntRange(from = 0, to = 1000) weight: Int,
+ @FloatRange(from = 0.0) textSize: Float,
+ color: Int?,
+ animate: Boolean,
+ duration: Long,
+ delay: Long,
+ onAnimationEnd: Runnable?
+ ) {
+ setTextStyle(
+ weight = weight,
+ textSize = textSize,
+ color = color,
+ animate = animate,
+ interpolator = null,
+ duration = duration,
+ delay = delay,
+ onAnimationEnd = onAnimationEnd
+ )
+ }
+
+ fun refreshFormat() {
+ Patterns.update(context)
+ val use24HourFormat = DateFormat.is24HourFormat(context)
+
+ format = when {
+ isSingleLineInternal && use24HourFormat -> Patterns.sClockView24
+ !isSingleLineInternal && use24HourFormat -> DOUBLE_LINE_FORMAT_24_HOUR
+ isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
+ else -> DOUBLE_LINE_FORMAT_12_HOUR
+ }
+
+ descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
+
+ refreshTime()
+ }
+
+ // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
+ // This is an optimization to ensure we only recompute the patterns when the inputs change.
+ private object Patterns {
+ var sClockView12: String? = null
+ var sClockView24: String? = null
+ var sCacheKey: String? = null
+
+ fun update(context: Context) {
+ val locale = Locale.getDefault()
+ val res = context.resources
+ val clockView12Skel = res.getString(R.string.clock_12hr_format)
+ val clockView24Skel = res.getString(R.string.clock_24hr_format)
+ val key = locale.toString() + clockView12Skel + clockView24Skel
+ if (key == sCacheKey) return
+
+ val clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel)
+ sClockView12 = clockView12
+
+ // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
+ // format. The following code removes the AM/PM indicator if we didn't want it.
+ if (!clockView12Skel.contains("a")) {
+ sClockView12 = clockView12.replace("a".toRegex(), "").trim { it <= ' ' }
+ }
+ sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel)
+ sCacheKey = key
+ }
+ }
+
+ interface DozeStateGetter {
+ val isDozing: Boolean
+ }
+}
+
+private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm"
+private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm"
+private const val DOZE_ANIM_DURATION: Long = 300
+private const val APPEAR_ANIM_DURATION: Long = 350
+private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
+private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 9238b8226bbc..25dcdf9aa561 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -190,11 +190,15 @@ public class KeyguardClockSwitch extends RelativeLayout {
}
}
- private void animateClockChange(boolean useLargeClock) {
+ private void updateClockViews(boolean useLargeClock, boolean animate) {
if (mClockInAnim != null) mClockInAnim.cancel();
if (mClockOutAnim != null) mClockOutAnim.cancel();
if (mStatusAreaAnim != null) mStatusAreaAnim.cancel();
+ mClockInAnim = null;
+ mClockOutAnim = null;
+ mStatusAreaAnim = null;
+
View in, out;
int direction = 1;
float statusAreaYTranslation;
@@ -214,6 +218,14 @@ public class KeyguardClockSwitch extends RelativeLayout {
removeView(out);
}
+ if (!animate) {
+ out.setAlpha(0f);
+ in.setAlpha(1f);
+ in.setVisibility(VISIBLE);
+ mStatusArea.setTranslationY(statusAreaYTranslation);
+ return;
+ }
+
mClockOutAnim = new AnimatorSet();
mClockOutAnim.setDuration(CLOCK_OUT_MILLIS);
mClockOutAnim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
@@ -273,7 +285,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
*
* @return true if desired clock appeared and false if it was already visible
*/
- boolean switchToClock(@ClockSize int clockSize) {
+ boolean switchToClock(@ClockSize int clockSize, boolean animate) {
if (mDisplayedClockSize != null && clockSize == mDisplayedClockSize) {
return false;
}
@@ -281,7 +293,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
// let's make sure clock is changed only after all views were laid out so we can
// translate them properly
if (mChildrenAreLaidOut) {
- animateClockChange(clockSize == LARGE);
+ updateClockViews(clockSize == LARGE, animate);
}
mDisplayedClockSize = clockSize;
@@ -293,7 +305,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
super.onLayout(changed, l, t, r, b);
if (mDisplayedClockSize != null && !mChildrenAreLaidOut) {
- animateClockChange(mDisplayedClockSize == LARGE);
+ updateClockViews(mDisplayedClockSize == LARGE, /* animate */ true);
}
mChildrenAreLaidOut = true;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 032da789518c..b7a5aae38515 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -275,11 +275,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
}
private void updateClockLayout() {
- int largeClockTopMargin = 0;
- if (mSmartspaceController.isEnabled()) {
- largeClockTopMargin = getContext().getResources().getDimensionPixelSize(
- R.dimen.keyguard_large_clock_top_margin);
- }
+ int largeClockTopMargin = getContext().getResources().getDimensionPixelSize(
+ R.dimen.keyguard_large_clock_top_margin);
+
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT,
MATCH_PARENT);
lp.topMargin = largeClockTopMargin;
@@ -290,17 +288,24 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
* Set which clock should be displayed on the keyguard. The other one will be automatically
* hidden.
*/
- public void displayClock(@KeyguardClockSwitch.ClockSize int clockSize) {
+ public void displayClock(@KeyguardClockSwitch.ClockSize int clockSize, boolean animate) {
if (!mCanShowDoubleLineClock && clockSize == KeyguardClockSwitch.LARGE) {
return;
}
- boolean appeared = mView.switchToClock(clockSize);
- if (appeared && clockSize == LARGE) {
+ boolean appeared = mView.switchToClock(clockSize, animate);
+ if (animate && appeared && clockSize == LARGE) {
mLargeClockViewController.animateAppear();
}
}
+ public void animateFoldToAod() {
+ if (mClockViewController != null) {
+ mClockViewController.animateFoldAppear();
+ mLargeClockViewController.animateFoldAppear();
+ }
+ }
+
/**
* If we're presenting a custom clock of just the default one.
*/
@@ -443,7 +448,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1) != 0;
if (!mCanShowDoubleLineClock) {
- mUiExecutor.execute(() -> displayClock(KeyguardClockSwitch.SMALL));
+ mUiExecutor.execute(() -> displayClock(KeyguardClockSwitch.SMALL, /* animate */ true));
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 67ef02a6029b..b84cb19b9468 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -866,7 +866,6 @@ public class KeyguardSecurityContainer extends FrameLayout {
((LayerDrawable) mUserSwitcher.getBackground()).getDrawable(1).setAlpha(255);
}
- anchor.setClickable(true);
anchor.setOnClickListener((v) -> {
if (mFalsingManager.isFalseTap(LOW_PENALTY)) return;
@@ -877,8 +876,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
public void onItemClick(AdapterView parent, View view, int pos, long id) {
if (mFalsingManager.isFalseTap(LOW_PENALTY)) return;
- // - 1 to account for the header view
- UserRecord user = adapter.getItem(pos - 1);
+ UserRecord user = adapter.getItem(pos);
if (!user.isCurrent) {
adapter.onUserListItemClicked(user);
}
@@ -907,9 +905,16 @@ public class KeyguardSecurityContainer extends FrameLayout {
== Configuration.ORIENTATION_PORTRAIT) {
updateViewGravity(mViewFlipper, Gravity.CENTER_HORIZONTAL);
updateViewGravity(mUserSwitcherViewGroup, Gravity.CENTER_HORIZONTAL);
+ mUserSwitcherViewGroup.setTranslationY(0);
} else {
updateViewGravity(mViewFlipper, Gravity.RIGHT | Gravity.BOTTOM);
- updateViewGravity(mUserSwitcherViewGroup, Gravity.LEFT | Gravity.TOP);
+ updateViewGravity(mUserSwitcherViewGroup, Gravity.LEFT | Gravity.CENTER_VERTICAL);
+
+ // Attempt to reposition a bit higher to make up for this frame being a bit lower
+ // on the device
+ int yTrans = mView.getContext().getResources().getDimensionPixelSize(
+ R.dimen.status_bar_height);
+ mUserSwitcherViewGroup.setTranslationY(-yTrans);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 986d0debb56a..8bf890d7df50 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -123,8 +123,17 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
* Set which clock should be displayed on the keyguard. The other one will be automatically
* hidden.
*/
- public void displayClock(@ClockSize int clockSize) {
- mKeyguardClockSwitchController.displayClock(clockSize);
+ public void displayClock(@ClockSize int clockSize, boolean animate) {
+ mKeyguardClockSwitchController.displayClock(clockSize, animate);
+ }
+
+ /**
+ * Performs fold to aod animation of the clocks (changes font weight from bold to thin).
+ * This animation is played when AOD is enabled and foldable device is fully folded, it is
+ * displayed on the outer screen
+ */
+ public void animateFoldToAod() {
+ mKeyguardClockSwitchController.animateFoldToAod();
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java
index dfb466788ca7..7b6ce3e1c951 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java
@@ -18,12 +18,10 @@ package com.android.keyguard;
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ListPopupWindow;
import android.widget.ListView;
-import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.plugins.FalsingManager;
@@ -68,12 +66,6 @@ public class KeyguardUserSwitcherPopupMenu extends ListPopupWindow {
// This will force the popupwindow to show upward instead of drop down
listView.addOnLayoutChangeListener(mLayoutListener);
- TextView header = (TextView) LayoutInflater.from(mContext).inflate(
- R.layout.keyguard_bouncer_user_switcher_item, listView, false);
- header.setText(mContext.getResources().getString(
- R.string.accessibility_multi_user_switch_switcher));
- listView.addHeaderView(header);
-
listView.setOnTouchListener((v, ev) -> {
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
return mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY);
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index cd57af4ae97b..6626f59aae8c 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -278,10 +278,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
mView.setContentDescription(mUnlockedLabel);
mView.setVisibility(View.VISIBLE);
} else if (mShowAodLockIcon) {
- if (wasShowingUnlock) {
- // transition to the unlock icon first
- mView.updateIcon(ICON_LOCK, false);
- }
mView.updateIcon(ICON_LOCK, true);
mView.setContentDescription(mLockedLabel);
mView.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 8c405ca6bd39..63962fa6da11 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -16,6 +16,7 @@
package com.android.systemui;
+import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.Application;
import android.app.Notification;
@@ -27,6 +28,7 @@ import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Process;
+import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
@@ -111,6 +113,13 @@ public class SystemUIApplication extends Application implements
ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
}
+ // Enable binder tracing on system server for calls originating from SysUI
+ try {
+ ActivityManager.getService().enableBinderTracing();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to enable binder tracing", e);
+ }
+
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 251c1e632f95..276790483861 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -121,7 +121,7 @@ public class SystemUIFactory {
.setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
.setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper())
.setRecentTasks(mWMComponent.getRecentTasks())
- .setSizeCompatUI(Optional.of(mWMComponent.getSizeCompatUI()))
+ .setCompatUI(Optional.of(mWMComponent.getCompatUI()))
.setDragAndDrop(Optional.of(mWMComponent.getDragAndDrop()));
} else {
// TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option
@@ -141,7 +141,7 @@ public class SystemUIFactory {
.setStartingSurface(Optional.ofNullable(null))
.setTaskSurfaceHelper(Optional.ofNullable(null))
.setRecentTasks(Optional.ofNullable(null))
- .setSizeCompatUI(Optional.ofNullable(null))
+ .setCompatUI(Optional.ofNullable(null))
.setDragAndDrop(Optional.ofNullable(null));
}
mSysUIComponent = builder.build();
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java
index 4aa46f1813dc..58cf35f2917c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java
@@ -16,12 +16,14 @@
package com.android.systemui.communal;
+import static com.android.systemui.communal.dagger.CommunalModule.COMMUNAL_CONDITIONS;
+
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.communal.conditions.CommunalConditionsMonitor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.condition.Monitor;
import com.google.android.collect.Lists;
@@ -31,6 +33,7 @@ import java.util.Iterator;
import java.util.concurrent.Executor;
import javax.inject.Inject;
+import javax.inject.Named;
/**
* A Monitor for reporting a {@link CommunalSource} presence.
@@ -42,7 +45,7 @@ public class CommunalSourceMonitor {
// A list of {@link Callback} that have registered to receive updates.
private final ArrayList<WeakReference<Callback>> mCallbacks = Lists.newArrayList();
- private final CommunalConditionsMonitor mConditionsMonitor;
+ private final Monitor mConditionsMonitor;
private final Executor mExecutor;
private CommunalSource mCurrentSource;
@@ -53,7 +56,7 @@ public class CommunalSourceMonitor {
// Whether the class is currently listening for condition changes.
private boolean mListeningForConditions = false;
- private final CommunalConditionsMonitor.Callback mConditionsCallback =
+ private final Monitor.Callback mConditionsCallback =
allConditionsMet -> {
if (mAllCommunalConditionsMet != allConditionsMet) {
if (DEBUG) Log.d(TAG, "communal conditions changed: " + allConditionsMet);
@@ -66,7 +69,7 @@ public class CommunalSourceMonitor {
@VisibleForTesting
@Inject
public CommunalSourceMonitor(@Main Executor executor,
- CommunalConditionsMonitor communalConditionsMonitor) {
+ @Named(COMMUNAL_CONDITIONS) Monitor communalConditionsMonitor) {
mExecutor = executor;
mConditionsMonitor = communalConditionsMonitor;
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
index f27ae344eb24..e1f1ac42884d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
@@ -34,6 +34,8 @@ import com.android.systemui.idle.AmbientLightModeMonitor;
import com.android.systemui.idle.LightSensorEventsDebounceAlgorithm;
import com.android.systemui.idle.dagger.IdleViewComponent;
import com.android.systemui.util.condition.Condition;
+import com.android.systemui.util.condition.Monitor;
+import com.android.systemui.util.condition.dagger.MonitorComponent;
import java.util.Collections;
import java.util.HashSet;
@@ -135,4 +137,14 @@ public interface CommunalModule {
return Optional.empty();
}
}
+
+ /** */
+ @Provides
+ @Named(COMMUNAL_CONDITIONS)
+ static Monitor provideCommunalSourceMonitor(
+ @Named(COMMUNAL_CONDITIONS) Set<Condition> communalConditions,
+ MonitorComponent.Factory factory) {
+ final MonitorComponent component = factory.create(communalConditions, new HashSet<>());
+ return component.getMonitor();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 2e0c0f45a960..e3376789c0d1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -33,6 +33,7 @@ import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.compatui.CompatUI;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.draganddrop.DragAndDrop;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
@@ -40,7 +41,6 @@ import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.startingsurface.StartingSurface;
import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
@@ -112,7 +112,7 @@ public interface SysUIComponent {
Builder setRecentTasks(Optional<RecentTasks> r);
@BindsInstance
- Builder setSizeCompatUI(Optional<SizeCompatUI> s);
+ Builder setCompatUI(Optional<CompatUI> s);
@BindsInstance
Builder setDragAndDrop(Optional<DragAndDrop> d);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 90a3ad225f51..b815d4e9884b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -25,6 +25,7 @@ import com.android.wm.shell.ShellInit;
import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.compatui.CompatUI;
import com.android.wm.shell.dagger.TvWMShellModule;
import com.android.wm.shell.dagger.WMShellModule;
import com.android.wm.shell.dagger.WMSingleton;
@@ -35,7 +36,6 @@ import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.startingsurface.StartingSurface;
import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
@@ -119,7 +119,7 @@ public interface WMComponent {
Optional<RecentTasks> getRecentTasks();
@WMSingleton
- SizeCompatUI getSizeCompatUI();
+ CompatUI getCompatUI();
@WMSingleton
DragAndDrop getDragAndDrop();
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 471a327ad987..b2fe3bb94dd3 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -37,10 +37,14 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.unfold.FoldAodAnimationController;
+import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus;
+import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.util.AlarmTimeout;
import com.android.systemui.util.wakelock.WakeLock;
import java.util.Calendar;
+import java.util.Optional;
import javax.inject.Inject;
@@ -49,7 +53,8 @@ import javax.inject.Inject;
*/
@DozeScope
public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
- ConfigurationController.ConfigurationListener, StatusBarStateController.StateListener {
+ ConfigurationController.ConfigurationListener, FoldAodAnimationStatus,
+ StatusBarStateController.StateListener {
// if enabled, calls dozeTimeTick() whenever the time changes:
private static final boolean BURN_IN_TESTING_ENABLED = false;
private static final long TIME_TICK_DEADLINE_MILLIS = 90 * 1000; // 1.5min
@@ -57,6 +62,7 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
private final DozeHost mHost;
private final Handler mHandler;
private final WakeLock mWakeLock;
+ private final FoldAodAnimationController mFoldAodAnimationController;
private DozeMachine mMachine;
private final AlarmTimeout mTimeTicker;
private final boolean mCanAnimateTransition;
@@ -100,6 +106,7 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
DozeParameters params, KeyguardUpdateMonitor keyguardUpdateMonitor,
DozeLog dozeLog, TunerService tunerService,
StatusBarStateController statusBarStateController,
+ Optional<SysUIUnfoldComponent> sysUiUnfoldComponent,
ConfigurationController configurationController) {
mContext = context;
mWakeLock = wakeLock;
@@ -118,12 +125,23 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
mConfigurationController = configurationController;
mConfigurationController.addCallback(this);
+
+ mFoldAodAnimationController = sysUiUnfoldComponent
+ .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
+
+ if (mFoldAodAnimationController != null) {
+ mFoldAodAnimationController.addCallback(this);
+ }
}
@Override
public void destroy() {
mTunerService.removeTunable(this);
mConfigurationController.removeCallback(this);
+
+ if (mFoldAodAnimationController != null) {
+ mFoldAodAnimationController.removeCallback(this);
+ }
}
@Override
@@ -142,7 +160,8 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
&& (mKeyguardShowing || mDozeParameters.shouldControlUnlockedScreenOff())
&& !mHost.isPowerSaveActive();
mDozeParameters.setControlScreenOffAnimation(controlScreenOff);
- mHost.setAnimateScreenOff(controlScreenOff);
+ mHost.setAnimateScreenOff(controlScreenOff
+ && mDozeParameters.shouldAnimateDozingChange());
}
}
@@ -299,4 +318,9 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
public void onStatePostChange() {
updateAnimateScreenOff();
}
+
+ @Override
+ public void onFoldToAodAnimationChanged() {
+ updateAnimateScreenOff();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerView.java
new file mode 100644
index 000000000000..bc1f772e14bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerView.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+/**
+ * {@link DreamOverlayContainerView} contains a dream overlay and its status bar.
+ */
+public class DreamOverlayContainerView extends ConstraintLayout {
+ public DreamOverlayContainerView(Context context) {
+ this(context, null);
+ }
+
+ public DreamOverlayContainerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public DreamOverlayContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public DreamOverlayContainerView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 8f0ea2fb2f87..20ccacc49fc6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -29,11 +29,11 @@ import android.view.WindowInsets;
import android.view.WindowManager;
import androidx.annotation.NonNull;
-import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.PhoneWindow;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import java.util.concurrent.Executor;
@@ -54,10 +54,12 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
private final Executor mExecutor;
// The state controller informs the service of updates to the overlays present.
private final DreamOverlayStateController mStateController;
+ // The component used to resolve dream overlay dependencies.
+ private final DreamOverlayComponent mDreamOverlayComponent;
- // The window is populated once the dream informs the service it has begun dreaming.
- private Window mWindow;
- private ConstraintLayout mLayout;
+ // The dream overlay's content view, which is located below the status bar (in z-order) and is
+ // the space into which widgets are placed.
+ private ViewGroup mDreamOverlayContentView;
private final DreamOverlayStateController.Callback mOverlayStateCallback =
new DreamOverlayStateController.Callback() {
@@ -90,12 +92,12 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
new ViewTreeObserver.OnComputeInternalInsetsListener() {
@Override
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
- if (mLayout != null) {
+ if (mDreamOverlayContentView != null) {
inoutInfo.setTouchableInsets(
ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
final Region region = new Region();
- for (int i = 0; i < mLayout.getChildCount(); i++) {
- View child = mLayout.getChildAt(i);
+ for (int i = 0; i < mDreamOverlayContentView.getChildCount(); i++) {
+ View child = mDreamOverlayContentView.getChildAt(i);
final Rect rect = new Rect();
child.getGlobalVisibleRect(rect);
region.op(rect, Region.Op.UNION);
@@ -106,16 +108,30 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
}
};
+ @Inject
+ public DreamOverlayService(
+ Context context,
+ @Main Executor executor,
+ DreamOverlayStateController overlayStateController,
+ DreamOverlayComponent.Factory dreamOverlayComponentFactory) {
+ mContext = context;
+ mExecutor = executor;
+ mStateController = overlayStateController;
+ mDreamOverlayComponent = dreamOverlayComponentFactory.create();
+
+ mStateController.addCallback(mOverlayStateCallback);
+ }
+
@Override
public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
mExecutor.execute(() -> addOverlayWindowLocked(layoutParams));
}
private void reloadOverlaysLocked() {
- if (mLayout == null) {
+ if (mDreamOverlayContentView == null) {
return;
}
- mLayout.removeAllViews();
+ mDreamOverlayContentView.removeAllViews();
for (OverlayProvider overlayProvider : mStateController.getOverlays()) {
addOverlay(overlayProvider);
}
@@ -129,31 +145,30 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
* into the dream window.
*/
private void addOverlayWindowLocked(WindowManager.LayoutParams layoutParams) {
- mWindow = new PhoneWindow(mContext);
- mWindow.setAttributes(layoutParams);
- mWindow.setWindowManager(null, layoutParams.token, "DreamOverlay", true);
+ final PhoneWindow window = new PhoneWindow(mContext);
+ window.setAttributes(layoutParams);
+ window.setWindowManager(null, layoutParams.token, "DreamOverlay", true);
- mWindow.setBackgroundDrawable(new ColorDrawable(0));
+ window.setBackgroundDrawable(new ColorDrawable(0));
- mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
- mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
- mWindow.requestFeature(Window.FEATURE_NO_TITLE);
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+ window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
+ window.requestFeature(Window.FEATURE_NO_TITLE);
// Hide all insets when the dream is showing
- mWindow.getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
- mWindow.setDecorFitsSystemWindows(false);
+ window.getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
+ window.setDecorFitsSystemWindows(false);
if (DEBUG) {
Log.d(TAG, "adding overlay window to dream");
}
- mLayout = new ConstraintLayout(mContext);
- mLayout.setLayoutParams(new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
- mLayout.addOnAttachStateChangeListener(mRootViewAttachListener);
- mWindow.setContentView(mLayout);
+ window.setContentView(mDreamOverlayComponent.getDreamOverlayContainerView());
+ mDreamOverlayContentView = mDreamOverlayComponent.getDreamOverlayContentView();
+
+ mDreamOverlayComponent.getDreamOverlayStatusBarViewController().init();
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
- windowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
+ windowManager.addView(window.getDecorView(), window.getAttributes());
mExecutor.execute(this::reloadOverlaysLocked);
}
@@ -163,11 +178,11 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
(view, layoutParams) -> {
// Always move UI related work to the main thread.
mExecutor.execute(() -> {
- if (mLayout == null) {
+ if (mDreamOverlayContentView == null) {
return;
}
- mLayout.addView(view, layoutParams);
+ mDreamOverlayContentView.addView(view, layoutParams);
});
},
() -> {
@@ -178,15 +193,6 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
});
}
- @Inject
- public DreamOverlayService(Context context, @Main Executor executor,
- DreamOverlayStateController overlayStateController) {
- mContext = context;
- mExecutor = executor;
- mStateController = overlayStateController;
- mStateController.addCallback(mOverlayStateCallback);
- }
-
@Override
public void onDestroy() {
mStateController.removeCallback(mOverlayStateCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
new file mode 100644
index 000000000000..9847ef633bc1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.systemui.battery.BatteryMeterView;
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
+
+/**
+ * {@link DreamOverlayStatusBarView} is the view responsible for displaying the status bar in a
+ * dream. The status bar includes status icons such as battery and wifi.
+ */
+public class DreamOverlayStatusBarView extends ConstraintLayout implements
+ BatteryStateChangeCallback {
+
+ private BatteryMeterView mBatteryView;
+ private ImageView mWifiStatusView;
+
+ public DreamOverlayStatusBarView(Context context) {
+ this(context, null);
+ }
+
+ public DreamOverlayStatusBarView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public DreamOverlayStatusBarView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public DreamOverlayStatusBarView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mBatteryView = Preconditions.checkNotNull(findViewById(R.id.dream_overlay_battery),
+ "R.id.dream_overlay_battery must not be null");
+ mWifiStatusView = Preconditions.checkNotNull(findViewById(R.id.dream_overlay_wifi_status),
+ "R.id.dream_overlay_wifi_status must not be null");
+
+ mWifiStatusView.setImageDrawable(getContext().getDrawable(R.drawable.ic_signal_wifi_off));
+ }
+
+ /**
+ * Whether to show the battery percent text next to the battery status icons.
+ * @param show True if the battery percent text should be shown.
+ */
+ void showBatteryPercentText(boolean show) {
+ mBatteryView.setForceShowPercent(show);
+ }
+
+ /**
+ * Whether to show the wifi status icon.
+ * @param show True if the wifi status icon should be shown.
+ */
+ void showWifiStatus(boolean show) {
+ // Only show the wifi status icon when wifi isn't available.
+ mWifiStatusView.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
new file mode 100644
index 000000000000..5674b9f3f9fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+
+import com.android.systemui.battery.BatteryMeterViewController;
+import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.dreams.dagger.DreamOverlayModule;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.util.ViewController;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * View controller for {@link DreamOverlayStatusBarView}.
+ */
+@DreamOverlayComponent.DreamOverlayScope
+public class DreamOverlayStatusBarViewController extends ViewController<DreamOverlayStatusBarView> {
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "WIFI_STATUS_" }, value = {
+ WIFI_STATUS_UNKNOWN,
+ WIFI_STATUS_UNAVAILABLE,
+ WIFI_STATUS_AVAILABLE
+ })
+ private @interface WifiStatus {}
+ private static final int WIFI_STATUS_UNKNOWN = 0;
+ private static final int WIFI_STATUS_UNAVAILABLE = 1;
+ private static final int WIFI_STATUS_AVAILABLE = 2;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "BATTERY_STATUS_" }, value = {
+ BATTERY_STATUS_UNKNOWN,
+ BATTERY_STATUS_NOT_CHARGING,
+ BATTERY_STATUS_CHARGING
+ })
+ private @interface BatteryStatus {}
+ private static final int BATTERY_STATUS_UNKNOWN = 0;
+ private static final int BATTERY_STATUS_NOT_CHARGING = 1;
+ private static final int BATTERY_STATUS_CHARGING = 2;
+
+ private final BatteryController mBatteryController;
+ private final BatteryMeterViewController mBatteryMeterViewController;
+ private final ConnectivityManager mConnectivityManager;
+ private final boolean mShowPercentAvailable;
+
+ private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
+
+ private final NetworkCallback mNetworkCallback = new NetworkCallback() {
+ @Override
+ public void onCapabilitiesChanged(
+ Network network, NetworkCapabilities networkCapabilities) {
+ onWifiAvailabilityChanged(
+ networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI));
+ }
+
+ @Override
+ public void onAvailable(Network network) {
+ onWifiAvailabilityChanged(true);
+ }
+
+ @Override
+ public void onLost(Network network) {
+ onWifiAvailabilityChanged(false);
+ }
+ };
+
+ private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback =
+ new BatteryController.BatteryStateChangeCallback() {
+ @Override
+ public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+ DreamOverlayStatusBarViewController.this.onBatteryLevelChanged(charging);
+ }
+ };
+
+ private @WifiStatus int mWifiStatus = WIFI_STATUS_UNKNOWN;
+ private @BatteryStatus int mBatteryStatus = BATTERY_STATUS_UNKNOWN;
+
+ @Inject
+ public DreamOverlayStatusBarViewController(
+ Context context,
+ DreamOverlayStatusBarView view,
+ BatteryController batteryController,
+ @Named(DreamOverlayModule.DREAM_OVERLAY_BATTERY_CONTROLLER)
+ BatteryMeterViewController batteryMeterViewController,
+ ConnectivityManager connectivityManager) {
+ super(view);
+ mBatteryController = batteryController;
+ mBatteryMeterViewController = batteryMeterViewController;
+ mConnectivityManager = connectivityManager;
+
+ mShowPercentAvailable = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_battery_percentage_setting_available);
+ }
+
+ @Override
+ protected void onInit() {
+ super.onInit();
+ mBatteryMeterViewController.init();
+ }
+
+ @Override
+ protected void onViewAttached() {
+ mBatteryController.addCallback(mBatteryStateChangeCallback);
+ mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
+
+ NetworkCapabilities capabilities =
+ mConnectivityManager.getNetworkCapabilities(
+ mConnectivityManager.getActiveNetwork());
+ onWifiAvailabilityChanged(
+ capabilities != null
+ && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI));
+ }
+
+ @Override
+ protected void onViewDetached() {
+ mBatteryController.removeCallback(mBatteryStateChangeCallback);
+ mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+ }
+
+ /**
+ * Wifi availability has changed. Update the wifi status icon as appropriate.
+ * @param available Whether wifi is available.
+ */
+ private void onWifiAvailabilityChanged(boolean available) {
+ final int newWifiStatus = available ? WIFI_STATUS_AVAILABLE : WIFI_STATUS_UNAVAILABLE;
+ if (mWifiStatus != newWifiStatus) {
+ mWifiStatus = newWifiStatus;
+ mView.showWifiStatus(mWifiStatus == WIFI_STATUS_UNAVAILABLE);
+ }
+ }
+
+ /**
+ * The battery level has changed. Update the battery status icon as appropriate.
+ * @param charging Whether the battery is currently charging.
+ */
+ private void onBatteryLevelChanged(boolean charging) {
+ final int newBatteryStatus =
+ charging ? BATTERY_STATUS_CHARGING : BATTERY_STATUS_NOT_CHARGING;
+ if (mBatteryStatus != newBatteryStatus) {
+ mBatteryStatus = newBatteryStatus;
+ mView.showBatteryPercentText(
+ mBatteryStatus == BATTERY_STATUS_CHARGING && mShowPercentAvailable);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index 7bf2361e471c..ff5beb54bd89 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -21,8 +21,6 @@ import dagger.Module;
/**
* Dagger Module providing Communal-related functionality.
*/
-@Module(subcomponents = {
- AppWidgetOverlayComponent.class,
-})
+@Module(subcomponents = {AppWidgetOverlayComponent.class, DreamOverlayComponent.class})
public interface DreamModule {
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
new file mode 100644
index 000000000000..a3a446a0dbca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.view.ViewGroup;
+
+import com.android.systemui.dreams.DreamOverlayContainerView;
+import com.android.systemui.dreams.DreamOverlayStatusBarViewController;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
+
+import dagger.Subcomponent;
+
+/**
+ * Dagger subcomponent for {@link DreamOverlayModule}.
+ */
+@Subcomponent(modules = {DreamOverlayModule.class})
+@DreamOverlayComponent.DreamOverlayScope
+public interface DreamOverlayComponent {
+ /** Simple factory for {@link DreamOverlayComponent}. */
+ @Subcomponent.Factory
+ interface Factory {
+ DreamOverlayComponent create();
+ }
+
+ /** Scope annotation for singleton items within the {@link DreamOverlayComponent}. */
+ @Documented
+ @Retention(RUNTIME)
+ @Scope
+ @interface DreamOverlayScope {}
+
+ /** Builds a {@link DreamOverlayContainerView} */
+ @DreamOverlayScope
+ DreamOverlayContainerView getDreamOverlayContainerView();
+
+ /** Builds a content view for dream overlays */
+ @DreamOverlayScope
+ ViewGroup getDreamOverlayContentView();
+
+ /** Builds a {@link DreamOverlayStatusBarViewController}. */
+ @DreamOverlayScope
+ DreamOverlayStatusBarViewController getDreamOverlayStatusBarViewController();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
new file mode 100644
index 000000000000..d0a8fad9b1e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.dagger;
+
+import android.content.ContentResolver;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.systemui.battery.BatteryMeterView;
+import com.android.systemui.battery.BatteryMeterViewController;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.DreamOverlayContainerView;
+import com.android.systemui.dreams.DreamOverlayStatusBarView;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.tuner.TunerService;
+
+import javax.inject.Named;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Dagger module for {@link DreamOverlayComponent}. */
+@Module
+public abstract class DreamOverlayModule {
+ private static final String DREAM_OVERLAY_BATTERY_VIEW = "dream_overlay_battery_view";
+ public static final String DREAM_OVERLAY_BATTERY_CONTROLLER =
+ "dream_overlay_battery_controller";
+
+ /** */
+ @Provides
+ @DreamOverlayComponent.DreamOverlayScope
+ public static DreamOverlayContainerView providesDreamOverlayContainerView(
+ LayoutInflater layoutInflater) {
+ return Preconditions.checkNotNull((DreamOverlayContainerView)
+ layoutInflater.inflate(R.layout.dream_overlay_container, null),
+ "R.layout.dream_layout_container could not be properly inflated");
+ }
+
+ /** */
+ @Provides
+ @DreamOverlayComponent.DreamOverlayScope
+ public static ViewGroup providesDreamOverlayContentView(DreamOverlayContainerView view) {
+ return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_content),
+ "R.id.dream_overlay_content must not be null");
+ }
+
+ /** */
+ @Provides
+ @DreamOverlayComponent.DreamOverlayScope
+ public static DreamOverlayStatusBarView providesDreamOverlayStatusBarView(
+ DreamOverlayContainerView view) {
+ return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_status_bar),
+ "R.id.status_bar must not be null");
+ }
+
+ /** */
+ @Provides
+ @DreamOverlayComponent.DreamOverlayScope
+ @Named(DREAM_OVERLAY_BATTERY_VIEW)
+ static BatteryMeterView providesBatteryMeterView(DreamOverlayContainerView view) {
+ return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_battery),
+ "R.id.battery must not be null");
+ }
+
+ /** */
+ @Provides
+ @DreamOverlayComponent.DreamOverlayScope
+ @Named(DREAM_OVERLAY_BATTERY_CONTROLLER)
+ static BatteryMeterViewController providesBatteryMeterViewController(
+ @Named(DREAM_OVERLAY_BATTERY_VIEW) BatteryMeterView batteryMeterView,
+ ConfigurationController configurationController,
+ TunerService tunerService,
+ BroadcastDispatcher broadcastDispatcher,
+ @Main Handler mainHandler,
+ ContentResolver contentResolver,
+ BatteryController batteryController) {
+ return new BatteryMeterViewController(
+ batteryMeterView,
+ configurationController,
+ tunerService,
+ broadcastDispatcher,
+ mainHandler,
+ contentResolver,
+ batteryController);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c592431d360e..0c9e3157f4d1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -122,6 +122,7 @@ import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.unfold.FoldAodAnimationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
import com.android.systemui.util.DeviceConfigProxy;
@@ -131,7 +132,6 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Optional;
import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicInteger;
import dagger.Lazy;
@@ -439,7 +439,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
private boolean mInGestureNavigationMode;
private boolean mWakeAndUnlocking;
- private IKeyguardDrawnCallback mDrawnCallback;
+ private Runnable mWakeAndUnlockingDrawnCallback;
private CharSequence mCustomMessage;
/**
@@ -817,7 +817,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
private DozeParameters mDozeParameters;
private final Optional<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealAnimation;
- private final AtomicInteger mPendingDrawnTasks = new AtomicInteger();
+ private final Optional<FoldAodAnimationController> mFoldAodAnimationController;
+ private final PendingDrawnTasksContainer mPendingDrawnTasks = new PendingDrawnTasksContainer();
private final KeyguardStateController mKeyguardStateController;
private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
@@ -877,8 +878,12 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode);
}));
mDozeParameters = dozeParameters;
- mUnfoldLightRevealAnimation = unfoldComponent.map(
- c -> c.getUnfoldLightRevealOverlayAnimation());
+
+ mUnfoldLightRevealAnimation = unfoldComponent
+ .map(SysUIUnfoldComponent::getUnfoldLightRevealOverlayAnimation);
+ mFoldAodAnimationController = unfoldComponent
+ .map(SysUIUnfoldComponent::getFoldAodAnimationController);
+
mStatusBarStateController = statusBarStateController;
statusBarStateController.addCallback(this);
@@ -1069,7 +1074,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
mDeviceInteractive = false;
mGoingToSleep = false;
mWakeAndUnlocking = false;
- mAnimatingScreenOff = mDozeParameters.shouldControlUnlockedScreenOff();
+ mAnimatingScreenOff = mDozeParameters.shouldAnimateDozingChange();
resetKeyguardDonePendingLocked();
mHideAnimationRun = false;
@@ -2230,14 +2235,14 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
IRemoteAnimationRunner runner = mKeyguardExitAnimationRunner;
mKeyguardExitAnimationRunner = null;
- if (mWakeAndUnlocking && mDrawnCallback != null) {
+ if (mWakeAndUnlocking && mWakeAndUnlockingDrawnCallback != null) {
// Hack level over 9000: To speed up wake-and-unlock sequence, force it to report
// the next draw from here so we don't have to wait for window manager to signal
// this to our ViewRootImpl.
mKeyguardViewControllerLazy.get().getViewRootImpl().setReportNextDraw();
- notifyDrawn(mDrawnCallback);
- mDrawnCallback = null;
+ mWakeAndUnlockingDrawnCallback.run();
+ mWakeAndUnlockingDrawnCallback = null;
}
LatencyTracker.getInstance(mContext)
@@ -2573,31 +2578,27 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleNotifyScreenTurningOn");
- if (mUnfoldLightRevealAnimation.isPresent()) {
- mPendingDrawnTasks.set(2); // unfold overlay and keyguard drawn
+ mPendingDrawnTasks.reset();
+ if (mUnfoldLightRevealAnimation.isPresent()) {
mUnfoldLightRevealAnimation.get()
- .onScreenTurningOn(() -> {
- if (mPendingDrawnTasks.decrementAndGet() == 0) {
- try {
- callback.onDrawn();
- } catch (RemoteException e) {
- Slog.w(TAG, "Exception calling onDrawn():", e);
- }
- }
- });
- } else {
- mPendingDrawnTasks.set(1); // only keyguard drawn
+ .onScreenTurningOn(mPendingDrawnTasks.registerTask("unfold-reveal"));
+ }
+
+ if (mFoldAodAnimationController.isPresent()) {
+ mFoldAodAnimationController.get()
+ .onScreenTurningOn(mPendingDrawnTasks.registerTask("fold-to-aod"));
}
mKeyguardViewControllerLazy.get().onScreenTurningOn();
if (callback != null) {
if (mWakeAndUnlocking) {
- mDrawnCallback = callback;
- } else {
- notifyDrawn(callback);
+ mWakeAndUnlockingDrawnCallback =
+ mPendingDrawnTasks.registerTask("wake-and-unlocking");
}
}
+
+ mPendingDrawnTasks.onTasksComplete(() -> notifyDrawn(callback));
}
Trace.endSection();
}
@@ -2606,6 +2607,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
Trace.beginSection("KeyguardViewMediator#handleNotifyScreenTurnedOn");
synchronized (this) {
if (DEBUG) Log.d(TAG, "handleNotifyScreenTurnedOn");
+
+ mPendingDrawnTasks.reset();
mKeyguardViewControllerLazy.get().onScreenTurnedOn();
}
Trace.endSection();
@@ -2614,18 +2617,18 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
private void handleNotifyScreenTurnedOff() {
synchronized (this) {
if (DEBUG) Log.d(TAG, "handleNotifyScreenTurnedOff");
- mDrawnCallback = null;
+ mWakeAndUnlockingDrawnCallback = null;
}
}
private void notifyDrawn(final IKeyguardDrawnCallback callback) {
Trace.beginSection("KeyguardViewMediator#notifyDrawn");
- if (mPendingDrawnTasks.decrementAndGet() == 0) {
- try {
+ try {
+ if (callback != null) {
callback.onDrawn();
- } catch (RemoteException e) {
- Slog.w(TAG, "Exception calling onDrawn():", e);
}
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Exception calling onDrawn():", e);
}
Trace.endSection();
}
@@ -2784,9 +2787,9 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
pw.print(" mHideAnimationRun: "); pw.println(mHideAnimationRun);
pw.print(" mPendingReset: "); pw.println(mPendingReset);
pw.print(" mPendingLock: "); pw.println(mPendingLock);
- pw.print(" mPendingDrawnTasks: "); pw.println(mPendingDrawnTasks.get());
+ pw.print(" mPendingDrawnTasks: "); pw.println(mPendingDrawnTasks.getPendingCount());
pw.print(" mWakeAndUnlocking: "); pw.println(mWakeAndUnlocking);
- pw.print(" mDrawnCallback: "); pw.println(mDrawnCallback);
+ pw.print(" mDrawnCallback: "); pw.println(mWakeAndUnlockingDrawnCallback);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/PendingDrawnTasksContainer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/PendingDrawnTasksContainer.kt
new file mode 100644
index 000000000000..bccd106db836
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/PendingDrawnTasksContainer.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard
+
+import android.os.Trace
+import java.util.concurrent.atomic.AtomicInteger
+import java.util.concurrent.atomic.AtomicReference
+
+/**
+ * Allows to wait for multiple callbacks and notify when the last one is executed
+ */
+class PendingDrawnTasksContainer {
+
+ private lateinit var pendingDrawnTasksCount: AtomicInteger
+ private var completionCallback: AtomicReference<Runnable> = AtomicReference()
+
+ /**
+ * Registers a task that we should wait for
+ * @return a runnable that should be invoked when the task is finished
+ */
+ fun registerTask(name: String): Runnable {
+ pendingDrawnTasksCount.incrementAndGet()
+
+ if (ENABLE_TRACE) {
+ Trace.beginAsyncSection("PendingDrawnTasksContainer#$name", 0)
+ }
+
+ return Runnable {
+ if (pendingDrawnTasksCount.decrementAndGet() == 0) {
+ val onComplete = completionCallback.getAndSet(null)
+ onComplete?.run()
+
+ if (ENABLE_TRACE) {
+ Trace.endAsyncSection("PendingDrawnTasksContainer#$name", 0)
+ }
+ }
+ }
+ }
+
+ /**
+ * Clears state and initializes the container
+ */
+ fun reset() {
+ // Create new objects in case if there are pending callbacks from the previous invocations
+ completionCallback = AtomicReference()
+ pendingDrawnTasksCount = AtomicInteger(0)
+ }
+
+ /**
+ * Starts waiting for all tasks to be completed
+ * When all registered tasks complete it will invoke the [onComplete] callback
+ */
+ fun onTasksComplete(onComplete: Runnable) {
+ completionCallback.set(onComplete)
+
+ if (pendingDrawnTasksCount.get() == 0) {
+ val currentOnComplete = completionCallback.getAndSet(null)
+ currentOnComplete?.run()
+ }
+ }
+
+ /**
+ * Returns current pending tasks count
+ */
+ fun getPendingCount(): Int = pendingDrawnTasksCount.get()
+}
+
+private const val ENABLE_TRACE = false
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 8026df747ff6..cc91384d93b8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -1251,6 +1251,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
private void onImeSwitcherClick(View v) {
mInputMethodManager.showInputMethodPickerFromSystem(
true /* showAuxiliarySubtypes */, mDisplayId);
+ mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);
};
private boolean onLongPressBackHome(View v) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index debd2ebb51be..d27b71673ce5 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -96,6 +96,9 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
@UiEvent(doc = "The overview button was pressed in the navigation bar.")
NAVBAR_OVERVIEW_BUTTON_TAP(535),
+ @UiEvent(doc = "The ime switcher button was pressed in the navigation bar.")
+ NAVBAR_IME_SWITCHER_BUTTON_TAP(923),
+
@UiEvent(doc = "The home button was long-pressed in the navigation bar.")
NAVBAR_HOME_BUTTON_LONGPRESS(536),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 5bd02cc207b9..10efec309971 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -48,6 +48,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
@@ -562,7 +563,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
return this;
}
- CustomTile build() {
+ @VisibleForTesting
+ public CustomTile build() {
if (mUserContext == null) {
throw new NullPointerException("UserContext cannot be null");
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index ac95bf50bb98..c2a9e3adb0b8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -169,7 +169,7 @@ public class QSFactoryImpl implements QSFactory {
mFgsManagerTileProvider = fgsManagerTileProvider;
}
- public QSTile createTile(String tileSpec) {
+ public final QSTile createTile(String tileSpec) {
QSTileImpl tile = createTileInternal(tileSpec);
if (tile != null) {
tile.initialize();
@@ -178,7 +178,7 @@ public class QSFactoryImpl implements QSFactory {
return tile;
}
- private QSTileImpl createTileInternal(String tileSpec) {
+ protected QSTileImpl createTileInternal(String tileSpec) {
// Stock tiles.
switch (tileSpec) {
case "wifi":
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt
index 75cf4d1ace8e..939a29711f45 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt
@@ -54,7 +54,7 @@ class FgsManagerTile @Inject constructor(
qsLogger: QSLogger?,
private val fgsManagerDialogFactory: FgsManagerDialogFactory,
private val runningFgsController: RunningFgsController
-) : QSTileImpl<QSTile.State?>(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+) : QSTileImpl<QSTile.State>(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger), RunningFgsController.Callback {
override fun handleInitialize() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 7dd24b48ace2..dd742b86a483 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -90,6 +90,7 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
@@ -485,6 +486,11 @@ public class InternetDialogController implements AccessPointController.AccessPoi
private Map<Integer, CharSequence> getUniqueSubscriptionDisplayNames(Context context) {
class DisplayInfo {
+ DisplayInfo(SubscriptionInfo subscriptionInfo, CharSequence originalName) {
+ this.subscriptionInfo = subscriptionInfo;
+ this.originalName = originalName;
+ }
+
public SubscriptionInfo subscriptionInfo;
public CharSequence originalName;
public CharSequence uniqueName;
@@ -498,12 +504,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi
// Filter out null values.
return (i != null && i.getDisplayName() != null);
})
- .map(i -> {
- DisplayInfo info = new DisplayInfo();
- info.subscriptionInfo = i;
- info.originalName = i.getDisplayName().toString().trim();
- return info;
- });
+ .map(i -> new DisplayInfo(i, i.getDisplayName().toString().trim()));
// A Unique set of display names
Set<CharSequence> uniqueNames = new HashSet<>();
@@ -582,7 +583,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi
return "";
}
- int resId = mapIconSets(config).get(iconKey).dataContentDescription;
+ int resId = Objects.requireNonNull(mapIconSets(config).get(iconKey)).dataContentDescription;
if (isCarrierNetworkActive()) {
SignalIcon.MobileIconGroup carrierMergedWifiIconGroup =
TelephonyIcons.CARRIER_MERGED_WIFI;
@@ -878,10 +879,8 @@ public class InternetDialogController implements AccessPointController.AccessPoi
if (accessPoints == null || accessPoints.size() == 0) {
mConnectedEntry = null;
mWifiEntriesCount = 0;
- if (mCallback != null) {
- mCallback.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */,
- false /* hasMoreEntry */);
- }
+ mCallback.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */,
+ false /* hasMoreEntry */);
return;
}
@@ -913,9 +912,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi
mConnectedEntry = connectedEntry;
mWifiEntriesCount = wifiEntries.size();
- if (mCallback != null) {
- mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry, hasMoreEntry);
- }
+ mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry, hasMoreEntry);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 3ed7e84af020..e7cd1e2dab3c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -76,6 +76,7 @@ import androidx.annotation.NonNull;
import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.util.ScreenshotHelper;
import com.android.systemui.Dumpable;
@@ -88,6 +89,7 @@ import com.android.systemui.navigationbar.NavigationBar;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.navigationbar.buttons.KeyButtonView;
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.shared.recents.IOverviewProxy;
@@ -161,6 +163,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
private final Optional<StartingSurface> mStartingSurface;
private final SmartspaceTransitionController mSmartspaceTransitionController;
private final Optional<RecentTasks> mRecentTasks;
+ private final UiEventLogger mUiEventLogger;
private Region mActiveNavBarRegion;
@@ -248,6 +251,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
mContext.getSystemService(InputMethodManager.class)
.showInputMethodPickerFromSystem(true /* showAuxiliarySubtypes */,
DEFAULT_DISPLAY);
+ mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);
}
@Override
@@ -560,6 +564,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
ShellTransitions shellTransitions,
ScreenLifecycle screenLifecycle,
SmartspaceTransitionController smartspaceTransitionController,
+ UiEventLogger uiEventLogger,
DumpManager dumpManager) {
super(broadcastDispatcher);
mContext = context;
@@ -581,6 +586,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
mOneHandedOptional = oneHandedOptional;
mShellTransitions = shellTransitions;
mRecentTasks = recentTasks;
+ mUiEventLogger = uiEventLogger;
// Assumes device always starts with back button until launcher tells it that it does not
mNavBarButtonAlpha = 1.0f;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index ce86953177e5..962c7fa6aea7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -102,12 +102,17 @@ class PrivacyDotViewController @Inject constructor(
configurationController.addCallback(object : ConfigurationController.ConfigurationListener {
override fun onLayoutDirectionChanged(isRtl: Boolean) {
- synchronized(this) {
- val corner = selectDesignatedCorner(nextViewState.rotation, isRtl)
- nextViewState = nextViewState.copy(
- layoutRtl = isRtl,
- designatedCorner = corner
- )
+ uiExecutor?.execute {
+ // If rtl changed, hide all dotes until the next state resolves
+ setCornerVisibilities(View.INVISIBLE)
+
+ synchronized(this) {
+ val corner = selectDesignatedCorner(nextViewState.rotation, isRtl)
+ nextViewState = nextViewState.copy(
+ layoutRtl = isRtl,
+ designatedCorner = corner
+ )
+ }
}
}
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
index f8d6c6d8ec10..b2e15f48004c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
@@ -25,8 +25,8 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.NotificationEntryManager
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.tuner.TunerService
@@ -44,7 +44,7 @@ class BypassHeadsUpNotifier @Inject constructor(
private val headsUpManager: HeadsUpManagerPhone,
private val notificationLockscreenUserManager: NotificationLockscreenUserManager,
private val mediaManager: NotificationMediaManager,
- private val entryManager: NotificationEntryManager,
+ private val commonNotifCollection: CommonNotifCollection,
tunerService: TunerService
) : StatusBarStateController.StateListener, NotificationMediaManager.MediaListener {
@@ -77,8 +77,7 @@ class BypassHeadsUpNotifier @Inject constructor(
override fun onPrimaryMetadataOrStateChanged(metadata: MediaMetadata?, state: Int) {
val previous = currentMediaEntry
- var newEntry = entryManager
- .getActiveNotificationUnfiltered(mediaManager.mediaNotificationKey)
+ var newEntry = commonNotifCollection.getEntry(mediaManager.mediaNotificationKey)
if (!NotificationMediaManager.isPlayingState(state)) {
newEntry = null
}
@@ -112,7 +111,7 @@ class BypassHeadsUpNotifier @Inject constructor(
// filter notifications invisible on Keyguard
return false
}
- if (entryManager.getActiveNotificationUnfiltered(entry.key) != null) {
+ if (commonNotifCollection.getEntry(entry.key) != null) {
// filter notifications not the active list currently
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index e3a4bf0170fb..2dc92764a4af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -51,6 +51,7 @@ public class StackStateAnimator {
public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400;
public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400;
+ public static final int ANIMATION_DURATION_FOLD_TO_AOD = 600;
public static final int ANIMATION_DURATION_PULSE_APPEAR =
KeyguardSliceView.DEFAULT_ANIM_DURATION;
public static final int ANIMATION_DURATION_BLOCKING_HELPER_FADE = 240;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 4b8b580eba0b..d87a02493a23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -243,6 +243,10 @@ public class DozeParameters implements
return mScreenOffAnimationController.shouldShowLightRevealScrim();
}
+ public boolean shouldAnimateDozingChange() {
+ return mScreenOffAnimationController.shouldAnimateDozingChange();
+ }
+
/**
* Whether we're capable of controlling the screen off animation if we want to. This isn't
* possible if AOD isn't even enabled or if the flag is disabled.
@@ -324,6 +328,7 @@ public class DozeParameters implements
for (Callback callback : mCallbacks) {
callback.onAlwaysOnChange();
}
+ mScreenOffAnimationController.onAlwaysOnChanged(getAlwaysOn());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 097c0d1677f9..3b7063e6662f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -26,6 +26,7 @@ import static androidx.constraintlayout.widget.ConstraintSet.TOP;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
+import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE;
import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
@@ -34,6 +35,7 @@ import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
+import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_CLOSED;
import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPEN;
import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPENING;
@@ -1423,11 +1425,12 @@ public class NotificationPanelViewController extends PanelViewController {
.getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia();
boolean splitShadeWithActiveMedia =
mShouldUseSplitNotificationShade && mMediaDataManager.hasActiveMedia();
+ boolean shouldAnimateClockChange = mScreenOffAnimationController.shouldAnimateClockChange();
if ((hasVisibleNotifications && !mShouldUseSplitNotificationShade)
|| (splitShadeWithActiveMedia && !mDozing)) {
- mKeyguardStatusViewController.displayClock(SMALL);
+ mKeyguardStatusViewController.displayClock(SMALL, shouldAnimateClockChange);
} else {
- mKeyguardStatusViewController.displayClock(LARGE);
+ mKeyguardStatusViewController.displayClock(LARGE, shouldAnimateClockChange);
}
updateKeyguardStatusViewAlignment(true /* animate */);
int userIconHeight = mKeyguardQsUserSwitchController != null
@@ -1463,7 +1466,7 @@ public class NotificationPanelViewController extends PanelViewController {
mKeyguardStatusViewController.isClockTopAligned());
mClockPositionAlgorithm.run(mClockPositionResult);
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
- boolean animateClock = animate || mAnimateNextPositionUpdate;
+ boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
mKeyguardStatusViewController.updatePosition(
mClockPositionResult.clockX, mClockPositionResult.clockY,
mClockPositionResult.clockScale, animateClock);
@@ -3821,6 +3824,45 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
+ /**
+ * 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);
+ }
+
+ /**
+ * Starts fold to AOD animation
+ */
+ public void startFoldToAodAnimation(Runnable endAction) {
+ mView.animate()
+ .translationX(0)
+ .alpha(1f)
+ .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
+ .setInterpolator(EMPHASIZED_DECELERATE)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ endAction.run();
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ endAction.run();
+ }
+ })
+ .start();
+
+ mKeyguardStatusViewController.animateFoldToAod();
+ }
+
/** */
public void setImportantForAccessibility(int mode) {
mView.setImportantForAccessibility(mode);
@@ -3935,6 +3977,10 @@ public class NotificationPanelViewController extends PanelViewController {
mView.setAlpha(alpha);
}
+ public void resetTranslation() {
+ mView.setTranslationX(0f);
+ }
+
public ViewPropertyAnimator fadeOut(long startDelayMs, long durationMs, Runnable endAction) {
return mView.animate().alpha(0).setStartDelay(startDelayMs).setDuration(
durationMs).setInterpolator(Interpolators.ALPHA_OUT).withLayer().withEndAction(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index f67d18183c10..1e71ceb8cae8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -38,7 +38,6 @@ import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.window.StatusBarWindowView;
import com.android.systemui.util.leak.RotationUtils;
import java.util.Objects;
@@ -76,6 +75,7 @@ public class PhoneStatusBarView extends FrameLayout {
@Override
public void onFinishInflate() {
+ super.onFinishInflate();
mBattery = findViewById(R.id.battery);
mClock = findViewById(R.id.clock);
mCutoutSpace = findViewById(R.id.cutout_space_view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
index 497e7d78ac4e..e806ca0d9005 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
@@ -19,16 +19,23 @@ import android.view.View
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.unfold.FoldAodAnimationController
+import com.android.systemui.unfold.SysUIUnfoldComponent
+import java.util.Optional
import javax.inject.Inject
@SysUISingleton
class ScreenOffAnimationController @Inject constructor(
+ sysUiUnfoldComponent: Optional<SysUIUnfoldComponent>,
unlockedScreenOffAnimation: UnlockedScreenOffAnimationController,
private val wakefulnessLifecycle: WakefulnessLifecycle,
) : WakefulnessLifecycle.Observer {
- // TODO(b/202844967) add fold to aod animation here
- private val animations: List<ScreenOffAnimation> = listOf(unlockedScreenOffAnimation)
+ private val foldToAodAnimation: FoldAodAnimationController? = sysUiUnfoldComponent
+ .orElse(null)?.getFoldAodAnimationController()
+
+ private val animations: List<ScreenOffAnimation> =
+ listOfNotNull(foldToAodAnimation, unlockedScreenOffAnimation)
fun initialize(statusBar: StatusBar, lightRevealScrim: LightRevealScrim) {
animations.forEach { it.initialize(statusBar, lightRevealScrim) }
@@ -43,6 +50,19 @@ class ScreenOffAnimationController @Inject constructor(
}
/**
+ * Called when opaqueness of the light reveal scrim has change
+ * When [isOpaque] is true then scrim is visible and covers the screen
+ */
+ fun onScrimOpaqueChanged(isOpaque: Boolean) =
+ animations.forEach { it.onScrimOpaqueChanged(isOpaque) }
+
+ /**
+ * Called when always on display setting changed
+ */
+ fun onAlwaysOnChanged(alwaysOn: Boolean) =
+ animations.forEach { it.onAlwaysOnChanged(alwaysOn) }
+
+ /**
* If returns true we are taking over the screen off animation from display manager to SysUI.
* We can play our custom animation instead of default fade out animation.
*/
@@ -103,6 +123,12 @@ class ScreenOffAnimationController @Inject constructor(
animations.any { it.isAnimationPlaying() }
/**
+ * Return true to ignore requests to hide keyguard
+ */
+ fun isKeyguardHideDelayed(): Boolean =
+ animations.any { it.isKeyguardHideDelayed() }
+
+ /**
* Return true to make the StatusBar expanded so we can animate [LightRevealScrim]
*/
fun shouldShowLightRevealScrim(): Boolean =
@@ -145,6 +171,19 @@ class ScreenOffAnimationController @Inject constructor(
*/
fun shouldAnimateAodIcons(): Boolean =
animations.all { it.shouldAnimateAodIcons() }
+
+ /**
+ * Return true to animate doze state change, if returns false dozing will be applied without
+ * animation (sends only 0.0f or 1.0f dozing progress)
+ */
+ fun shouldAnimateDozingChange(): Boolean =
+ animations.all { it.shouldAnimateDozingChange() }
+
+ /**
+ * Return true to animate large <-> small clock transition
+ */
+ fun shouldAnimateClockChange(): Boolean =
+ animations.all { it.shouldAnimateClockChange() }
}
interface ScreenOffAnimation {
@@ -158,11 +197,17 @@ interface ScreenOffAnimation {
fun shouldPlayAnimation(): Boolean = false
fun isAnimationPlaying(): Boolean = false
+ fun onScrimOpaqueChanged(isOpaque: Boolean) {}
+ fun onAlwaysOnChanged(alwaysOn: Boolean) {}
+
fun shouldAnimateInKeyguard(): Boolean = false
fun animateInKeyguard(keyguardView: View, after: Runnable) = after.run()
+ fun isKeyguardHideDelayed(): Boolean = false
fun shouldHideScrimOnWakeUp(): Boolean = false
fun overrideNotificationsDozeAmount(): Boolean = false
fun shouldShowAodIconsWhenShade(): Boolean = false
fun shouldAnimateAodIcons(): Boolean = true
+ fun shouldAnimateDozingChange(): Boolean = true
+ fun shouldAnimateClockChange(): Boolean = true
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt
index f6e19bff8f9b..8cf7288c9cd5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt
@@ -49,8 +49,9 @@ class SplitShadeHeaderController @Inject constructor(
}
private val combinedHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)
- // TODO(b/194178072) Handle RSSI hiding when multi carrier
private val iconManager: StatusBarIconController.TintedIconManager
+ private val iconContainer: StatusIconContainer
+ private val carrierIconSlots: List<String>
private val qsCarrierGroupController: QSCarrierGroupController
private var visible = false
set(value) {
@@ -117,10 +118,19 @@ class SplitShadeHeaderController @Inject constructor(
batteryMeterViewController.ignoreTunerUpdates()
batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
- val iconContainer: StatusIconContainer = statusBar.findViewById(R.id.statusIcons)
+ iconContainer = statusBar.findViewById(R.id.statusIcons)
iconManager = StatusBarIconController.TintedIconManager(iconContainer, featureFlags)
iconManager.setTint(Utils.getColorAttrDefaultColor(statusBar.context,
android.R.attr.textColorPrimary))
+
+ carrierIconSlots = if (featureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)) {
+ listOf(
+ statusBar.context.getString(com.android.internal.R.string.status_bar_no_calling),
+ statusBar.context.getString(com.android.internal.R.string.status_bar_call_strength)
+ )
+ } else {
+ listOf(statusBar.context.getString(com.android.internal.R.string.status_bar_mobile))
+ }
qsCarrierGroupController = qsCarrierGroupControllerBuilder
.setQSCarrierGroup(statusBar.findViewById(R.id.carrier_group))
.build()
@@ -185,9 +195,20 @@ class SplitShadeHeaderController @Inject constructor(
private fun updateListeners() {
qsCarrierGroupController.setListening(visible)
if (visible) {
+ updateSingleCarrier(qsCarrierGroupController.isSingleCarrier)
+ qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) }
statusBarIconController.addIconGroup(iconManager)
} else {
+ qsCarrierGroupController.setOnSingleCarrierChangedListener(null)
statusBarIconController.removeIconGroup(iconManager)
}
}
+
+ private fun updateSingleCarrier(singleCarrier: Boolean) {
+ if (singleCarrier) {
+ iconContainer.removeIgnoredSlots(carrierIconSlots)
+ } else {
+ iconContainer.addIgnoredSlots(carrierIconSlots)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 6c0b717fb85c..331299628f61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -341,7 +341,7 @@ public class StatusBar extends CoreStartable implements
mStatusBarWindowState = state;
mStatusBarWindowHidden = state == WINDOW_STATE_HIDDEN;
mStatusBarHideIconsForBouncerManager.setStatusBarWindowHidden(mStatusBarWindowHidden);
- if (getStatusBarView() != null) {
+ if (mStatusBarView != null) {
// Should #updateHideIconsForBouncer always be called, regardless of whether we have a
// status bar view? If so, we can make #updateHideIconsForBouncer private.
mStatusBarHideIconsForBouncerManager.updateHideIconsForBouncer(/* animate= */ false);
@@ -1124,23 +1124,18 @@ public class StatusBar extends CoreStartable implements
// Set up CollapsedStatusBarFragment and PhoneStatusBarView
StatusBarInitializer initializer = mStatusBarComponent.getStatusBarInitializer();
initializer.setStatusBarViewUpdatedListener(
- new StatusBarInitializer.OnStatusBarViewUpdatedListener() {
- @Override
- public void onStatusBarViewUpdated(
- @NonNull PhoneStatusBarView statusBarView,
- @NonNull PhoneStatusBarViewController statusBarViewController) {
- mStatusBarView = statusBarView;
- mPhoneStatusBarViewController = statusBarViewController;
- mNotificationShadeWindowViewController.setStatusBarView(mStatusBarView);
- // Ensure we re-propagate panel expansion values to the panel controller and
- // any listeners it may have, such as PanelBar. This will also ensure we
- // re-display the notification panel if necessary (for example, if
- // a heads-up notification was being displayed and should continue being
- // displayed).
- mNotificationPanelViewController.updatePanelExpansionAndVisibility();
- setBouncerShowingForStatusBarComponents(mBouncerShowing);
- checkBarModes();
- }
+ (statusBarView, statusBarViewController) -> {
+ mStatusBarView = statusBarView;
+ mPhoneStatusBarViewController = statusBarViewController;
+ mNotificationShadeWindowViewController.setStatusBarView(mStatusBarView);
+ // Ensure we re-propagate panel expansion values to the panel controller and
+ // any listeners it may have, such as PanelBar. This will also ensure we
+ // re-display the notification panel if necessary (for example, if
+ // a heads-up notification was being displayed and should continue being
+ // displayed).
+ mNotificationPanelViewController.updatePanelExpansionAndVisibility();
+ setBouncerShowingForStatusBarComponents(mBouncerShowing);
+ checkBarModes();
});
initializer.initializeStatusBar(mStatusBarComponent);
@@ -1199,6 +1194,8 @@ public class StatusBar extends CoreStartable implements
Runnable updateOpaqueness = () -> {
mNotificationShadeWindowController.setLightRevealScrimOpaque(
mLightRevealScrim.isScrimOpaque());
+ mScreenOffAnimationController
+ .onScrimOpaqueChanged(mLightRevealScrim.isScrimOpaque());
};
if (opaque) {
// Delay making the view opaque for a frame, because it needs some time to render
@@ -1578,10 +1575,6 @@ public class StatusBar extends CoreStartable implements
Trace.endSection();
}
- protected PhoneStatusBarView getStatusBarView() {
- return mStatusBarView;
- }
-
public NotificationShadeWindowView getNotificationShadeWindowView() {
return mNotificationShadeWindowView;
}
@@ -2915,7 +2908,17 @@ public class StatusBar extends CoreStartable implements
showKeyguardImpl();
}
} else {
- return hideKeyguardImpl(force);
+ // During folding a foldable device this might be called as a result of
+ // 'onScreenTurnedOff' call for the inner display.
+ // In this case:
+ // * When phone is locked on folding: it doesn't make sense to hide keyguard as it
+ // will be immediately locked again
+ // * When phone is unlocked: we still don't want to execute hiding of the keyguard
+ // as the animation could prepare 'fake AOD' interface (without actually
+ // transitioning to keyguard state) and this might reset the view states
+ if (!mScreenOffAnimationController.isKeyguardHideDelayed()) {
+ return hideKeyguardImpl(force);
+ }
}
return false;
}
@@ -3078,6 +3081,7 @@ public class StatusBar extends CoreStartable implements
mNotificationPanelViewController.onAffordanceLaunchEnded();
mNotificationPanelViewController.cancelAnimation();
mNotificationPanelViewController.setAlpha(1f);
+ mNotificationPanelViewController.resetTranslation();
mNotificationPanelViewController.resetViewGroupFade();
updateDozingState();
updateScrimController();
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 b84e6e6f37cc..a4aeae9c0307 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -111,8 +111,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private final NavigationModeController mNavigationModeController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final KeyguardBouncer.Factory mKeyguardBouncerFactory;
- private final WakefulnessLifecycle mWakefulnessLifecycle;
- private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private final KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
private KeyguardMessageAreaController mKeyguardMessageAreaController;
private final Lazy<ShadeController> mShadeController;
@@ -244,8 +242,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
KeyguardStateController keyguardStateController,
NotificationMediaManager notificationMediaManager,
KeyguardBouncer.Factory keyguardBouncerFactory,
- WakefulnessLifecycle wakefulnessLifecycle,
- UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
KeyguardMessageAreaController.Factory keyguardMessageAreaFactory,
Lazy<ShadeController> shadeController) {
mContext = context;
@@ -260,8 +256,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mStatusBarStateController = sysuiStatusBarStateController;
mDockManager = dockManager;
mKeyguardBouncerFactory = keyguardBouncerFactory;
- mWakefulnessLifecycle = wakefulnessLifecycle;
- mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mKeyguardMessageAreaFactory = keyguardMessageAreaFactory;
mShadeController = shadeController;
}
@@ -1155,7 +1149,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
@Override
public ViewRootImpl getViewRootImpl() {
- return mStatusBar.getStatusBarView().getViewRootImpl();
+ return mNotificationShadeWindowController.getNotificationShadeView().getViewRootImpl();
}
public void launchPendingWakeupAction() {
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
new file mode 100644
index 000000000000..fb9df01a0251
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold
+
+import android.os.PowerManager
+import android.provider.Settings
+import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.ScreenLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.phone.ScreenOffAnimation
+import com.android.systemui.statusbar.phone.StatusBar
+import com.android.systemui.statusbar.policy.CallbackController
+import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus
+import com.android.systemui.util.settings.GlobalSettings
+import dagger.Lazy
+import javax.inject.Inject
+
+/**
+ * Controls folding to AOD animation: when AOD is enabled and foldable device is folded
+ * we play a special AOD animation on the outer screen
+ */
+@SysUIUnfoldScope
+class FoldAodAnimationController @Inject constructor(
+ private val screenLifecycle: ScreenLifecycle,
+ private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
+ private val wakefulnessLifecycle: WakefulnessLifecycle,
+ private val globalSettings: GlobalSettings
+) : ScreenLifecycle.Observer,
+ CallbackController<FoldAodAnimationStatus>,
+ ScreenOffAnimation,
+ WakefulnessLifecycle.Observer {
+
+ private var alwaysOnEnabled: Boolean = false
+ private var isScrimOpaque: Boolean = false
+ private lateinit var statusBar: StatusBar
+ private var pendingScrimReadyCallback: Runnable? = null
+
+ private var shouldPlayAnimation = false
+ private val statusListeners = arrayListOf<FoldAodAnimationStatus>()
+
+ private var isAnimationPlaying = false
+
+ override fun initialize(statusBar: StatusBar, lightRevealScrim: LightRevealScrim) {
+ this.statusBar = statusBar
+
+ screenLifecycle.addObserver(this)
+ wakefulnessLifecycle.addObserver(this)
+ }
+
+ /**
+ * Returns true if we should run fold to AOD animation
+ */
+ override fun shouldPlayAnimation(): Boolean =
+ shouldPlayAnimation
+
+ override fun startAnimation(): Boolean =
+ if (alwaysOnEnabled &&
+ wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD &&
+ globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0"
+ ) {
+ shouldPlayAnimation = true
+
+ isAnimationPlaying = true
+ statusBar.notificationPanelViewController.prepareFoldToAodAnimation()
+
+ statusListeners.forEach(FoldAodAnimationStatus::onFoldToAodAnimationChanged)
+
+ true
+ } else {
+ shouldPlayAnimation = false
+ false
+ }
+
+ override fun onStartedWakingUp() {
+ shouldPlayAnimation = false
+ isAnimationPlaying = false
+ }
+
+ /**
+ * Called when screen starts turning on, the contents of the screen might not be visible yet.
+ * This method reports back that the animation is ready in [onReady] callback.
+ *
+ * @param onReady callback when the animation is ready
+ * @see [com.android.systemui.keyguard.KeyguardViewMediator]
+ */
+ fun onScreenTurningOn(onReady: Runnable) {
+ if (shouldPlayAnimation) {
+ if (isScrimOpaque) {
+ onReady.run()
+ } else {
+ pendingScrimReadyCallback = onReady
+ }
+ } else {
+ // No animation, call ready callback immediately
+ onReady.run()
+ }
+ }
+
+ /**
+ * Called when keyguard scrim opaque changed
+ */
+ override fun onScrimOpaqueChanged(isOpaque: Boolean) {
+ isScrimOpaque = isOpaque
+
+ if (isOpaque) {
+ pendingScrimReadyCallback?.run()
+ pendingScrimReadyCallback = null
+ }
+ }
+
+ override fun onScreenTurnedOn() {
+ if (shouldPlayAnimation) {
+ statusBar.notificationPanelViewController.startFoldToAodAnimation {
+ // End action
+ isAnimationPlaying = false
+ keyguardViewMediatorLazy.get().maybeHandlePendingLock()
+ }
+ shouldPlayAnimation = false
+ }
+ }
+
+ override fun isAnimationPlaying(): Boolean =
+ isAnimationPlaying
+
+ override fun isKeyguardHideDelayed(): Boolean =
+ isAnimationPlaying()
+
+ override fun shouldShowAodIconsWhenShade(): Boolean =
+ shouldPlayAnimation()
+
+ override fun shouldAnimateAodIcons(): Boolean =
+ !shouldPlayAnimation()
+
+ override fun shouldAnimateDozingChange(): Boolean =
+ !shouldPlayAnimation()
+
+ override fun shouldAnimateClockChange(): Boolean =
+ !isAnimationPlaying()
+
+ /**
+ * Called when AOD status is changed
+ */
+ override fun onAlwaysOnChanged(alwaysOn: Boolean) {
+ alwaysOnEnabled = alwaysOn
+ }
+
+ override fun addCallback(listener: FoldAodAnimationStatus) {
+ statusListeners += listener
+ }
+
+ override fun removeCallback(listener: FoldAodAnimationStatus) {
+ statusListeners.remove(listener)
+ }
+
+ interface FoldAodAnimationStatus {
+ fun onFoldToAodAnimationChanged()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
index b53ab210424f..ccde3162b177 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
@@ -78,6 +78,8 @@ interface SysUIUnfoldComponent {
fun getStatusBarMoveFromCenterAnimationController(): StatusBarMoveFromCenterAnimationController
+ fun getFoldAodAnimationController(): FoldAodAnimationController
+
fun getUnfoldTransitionWallpaperController(): UnfoldTransitionWallpaperController
fun getUnfoldLightRevealOverlayAnimation(): UnfoldLightRevealOverlayAnimation
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
index a7e9cdbf1a18..8b6e982be55b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
@@ -23,7 +23,6 @@ import com.android.systemui.statusbar.policy.CallbackController;
import org.jetbrains.annotations.NotNull;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -38,7 +37,7 @@ import javax.inject.Inject;
public class Monitor implements CallbackController<Monitor.Callback> {
private final String mTag = getClass().getSimpleName();
- private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
+ private final ArrayList<Callback> mCallbacks = new ArrayList<>();
// Set of all conditions that need to be monitored.
private final Set<Condition> mConditions;
@@ -66,9 +65,9 @@ public class Monitor implements CallbackController<Monitor.Callback> {
mAllConditionsMet = newAllConditionsMet;
// Updates all callbacks.
- final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator();
+ final Iterator<Callback> iterator = mCallbacks.iterator();
while (iterator.hasNext()) {
- final Callback callback = iterator.next().get();
+ final Callback callback = iterator.next();
if (callback == null) {
iterator.remove();
} else {
@@ -78,7 +77,7 @@ public class Monitor implements CallbackController<Monitor.Callback> {
};
@Inject
- public Monitor(Set<Condition> conditions) {
+ public Monitor(Set<Condition> conditions, Set<Callback> callbacks) {
mConditions = conditions;
// If there is no condition, give green pass.
@@ -89,12 +88,20 @@ public class Monitor implements CallbackController<Monitor.Callback> {
// Initializes the conditions map and registers a callback for each condition.
mConditions.forEach((condition -> mConditionsMap.put(condition, false)));
+
+ if (callbacks == null) {
+ return;
+ }
+
+ for (Callback callback : callbacks) {
+ addCallback(callback);
+ }
}
@Override
public void addCallback(@NotNull Callback callback) {
if (shouldLog()) Log.d(mTag, "adding callback");
- mCallbacks.add(new WeakReference<>(callback));
+ mCallbacks.add(callback);
// Updates the callback immediately.
callback.onConditionsChanged(mAllConditionsMet);
@@ -109,9 +116,9 @@ public class Monitor implements CallbackController<Monitor.Callback> {
@Override
public void removeCallback(@NotNull Callback callback) {
if (shouldLog()) Log.d(mTag, "removing callback");
- final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator();
+ final Iterator<Callback> iterator = mCallbacks.iterator();
while (iterator.hasNext()) {
- final Callback cb = iterator.next().get();
+ final Callback cb = iterator.next();
if (cb == null || cb == callback) {
iterator.remove();
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalConditionsMonitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/dagger/MonitorComponent.java
index 1197816e24d2..fc67973fe278 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalConditionsMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/dagger/MonitorComponent.java
@@ -14,29 +14,33 @@
* limitations under the License.
*/
-package com.android.systemui.communal.conditions;
+package com.android.systemui.util.condition.dagger;
-
-import static com.android.systemui.communal.dagger.CommunalModule.COMMUNAL_CONDITIONS;
-
-import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.util.condition.Condition;
import com.android.systemui.util.condition.Monitor;
import java.util.Set;
-import javax.inject.Inject;
-import javax.inject.Named;
+import dagger.BindsInstance;
+import dagger.Subcomponent;
/**
- * A concrete implementation of {@Monitor} with conditions for monitoring when communal mode should
- * be enabled.
+ * Component for {@link Monitor}.
*/
-@SysUISingleton
-public class CommunalConditionsMonitor extends Monitor {
- @Inject
- public CommunalConditionsMonitor(
- @Named(COMMUNAL_CONDITIONS) Set<Condition> communalConditions) {
- super(communalConditions);
+@Subcomponent
+public interface MonitorComponent {
+ /**
+ * Factory for {@link MonitorComponent}.
+ */
+ @Subcomponent.Factory
+ interface Factory {
+ MonitorComponent create(@BindsInstance Set<Condition> conditions,
+ @BindsInstance Set<Monitor.Callback> callbacks);
}
+
+ /**
+ * Provides {@link Monitor}.
+ * @return
+ */
+ Monitor getMonitor();
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java b/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
index 981bf01164e3..7892d6eec98d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
@@ -18,6 +18,7 @@ package com.android.systemui.util.dagger;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.RingerModeTrackerImpl;
+import com.android.systemui.util.condition.dagger.MonitorComponent;
import com.android.systemui.util.wrapper.UtilWrapperModule;
import dagger.Binds;
@@ -26,6 +27,9 @@ import dagger.Module;
/** Dagger Module for code in the util package. */
@Module(includes = {
UtilWrapperModule.class
+ },
+ subcomponents = {
+ MonitorComponent.class,
})
public interface UtilModule {
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 6dd6d6d17d58..4600bc71459a 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -56,6 +56,7 @@ import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.SystemUiTraceProto;
import com.android.wm.shell.ShellCommandHandler;
+import com.android.wm.shell.compatui.CompatUI;
import com.android.wm.shell.draganddrop.DragAndDrop;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
@@ -66,7 +67,6 @@ import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.onehanded.OneHandedUiEventLogger;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.protolog.ShellProtoLogImpl;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
import com.android.wm.shell.splitscreen.SplitScreen;
import java.io.FileDescriptor;
@@ -114,7 +114,7 @@ public final class WMShell extends CoreStartable
private final Optional<OneHanded> mOneHandedOptional;
private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional;
private final Optional<ShellCommandHandler> mShellCommandHandler;
- private final Optional<SizeCompatUI> mSizeCompatUIOptional;
+ private final Optional<CompatUI> mCompatUIOptional;
private final Optional<DragAndDrop> mDragAndDropOptional;
private final CommandQueue mCommandQueue;
@@ -132,7 +132,7 @@ public final class WMShell extends CoreStartable
private KeyguardUpdateMonitorCallback mSplitScreenKeyguardCallback;
private KeyguardUpdateMonitorCallback mPipKeyguardCallback;
private KeyguardUpdateMonitorCallback mOneHandedKeyguardCallback;
- private KeyguardUpdateMonitorCallback mSizeCompatUIKeyguardCallback;
+ private KeyguardUpdateMonitorCallback mCompatUIKeyguardCallback;
private WakefulnessLifecycle.Observer mWakefulnessObserver;
@Inject
@@ -143,7 +143,7 @@ public final class WMShell extends CoreStartable
Optional<OneHanded> oneHandedOptional,
Optional<HideDisplayCutout> hideDisplayCutoutOptional,
Optional<ShellCommandHandler> shellCommandHandler,
- Optional<SizeCompatUI> sizeCompatUIOptional,
+ Optional<CompatUI> sizeCompatUIOptional,
Optional<DragAndDrop> dragAndDropOptional,
CommandQueue commandQueue,
ConfigurationController configurationController,
@@ -169,7 +169,7 @@ public final class WMShell extends CoreStartable
mWakefulnessLifecycle = wakefulnessLifecycle;
mProtoTracer = protoTracer;
mShellCommandHandler = shellCommandHandler;
- mSizeCompatUIOptional = sizeCompatUIOptional;
+ mCompatUIOptional = sizeCompatUIOptional;
mDragAndDropOptional = dragAndDropOptional;
mSysUiMainExecutor = sysUiMainExecutor;
}
@@ -185,7 +185,7 @@ public final class WMShell extends CoreStartable
mSplitScreenOptional.ifPresent(this::initSplitScreen);
mOneHandedOptional.ifPresent(this::initOneHanded);
mHideDisplayCutoutOptional.ifPresent(this::initHideDisplayCutout);
- mSizeCompatUIOptional.ifPresent(this::initSizeCompatUi);
+ mCompatUIOptional.ifPresent(this::initCompatUi);
mDragAndDropOptional.ifPresent(this::initDragAndDrop);
}
@@ -391,14 +391,14 @@ public final class WMShell extends CoreStartable
}
@VisibleForTesting
- void initSizeCompatUi(SizeCompatUI sizeCompatUI) {
- mSizeCompatUIKeyguardCallback = new KeyguardUpdateMonitorCallback() {
+ void initCompatUi(CompatUI sizeCompatUI) {
+ mCompatUIKeyguardCallback = new KeyguardUpdateMonitorCallback() {
@Override
public void onKeyguardOccludedChanged(boolean occluded) {
sizeCompatUI.onKeyguardOccludedChanged(occluded);
}
};
- mKeyguardUpdateMonitor.registerCallback(mSizeCompatUIKeyguardCallback);
+ mKeyguardUpdateMonitor.registerCallback(mCompatUIKeyguardCallback);
}
void initDragAndDrop(DragAndDrop dragAndDrop) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index e967033b69a2..74e0f4002026 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -264,7 +264,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
reset(mView);
observer.onChange(true);
mExecutor.runAllReady();
- verify(mView).switchToClock(KeyguardClockSwitch.SMALL);
+ verify(mView).switchToClock(KeyguardClockSwitch.SMALL, /* animate */ true);
}
private void verifyAttachment(VerificationMode times) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index e4336fe07dbb..8717a0eaf57f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -253,8 +253,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
}
@Test
- public void switchingToBigClock_makesSmallClockDisappear() {
- mKeyguardClockSwitch.switchToClock(LARGE);
+ public void switchingToBigClockWithAnimation_makesSmallClockDisappear() {
+ mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true);
mKeyguardClockSwitch.mClockInAnim.end();
mKeyguardClockSwitch.mClockOutAnim.end();
@@ -265,8 +265,17 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
}
@Test
- public void switchingToSmallClock_makesBigClockDisappear() {
- mKeyguardClockSwitch.switchToClock(SMALL);
+ public void switchingToBigClockNoAnimation_makesSmallClockDisappear() {
+ mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ false);
+
+ assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
+ assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
+ assertThat(mClockFrame.getAlpha()).isEqualTo(0);
+ }
+
+ @Test
+ public void switchingToSmallClockWithAnimation_makesBigClockDisappear() {
+ mKeyguardClockSwitch.switchToClock(SMALL, /* animate */ true);
mKeyguardClockSwitch.mClockInAnim.end();
mKeyguardClockSwitch.mClockOutAnim.end();
@@ -279,8 +288,19 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
}
@Test
+ public void switchingToSmallClockNoAnimation_makesBigClockDisappear() {
+ mKeyguardClockSwitch.switchToClock(SMALL, false);
+
+ assertThat(mClockFrame.getAlpha()).isEqualTo(1);
+ assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE);
+ // only big clock is removed at switch
+ assertThat(mLargeClockFrame.getParent()).isNull();
+ assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
+ }
+
+ @Test
public void switchingToBigClock_returnsTrueOnlyWhenItWasNotVisibleBefore() {
- assertThat(mKeyguardClockSwitch.switchToClock(LARGE)).isTrue();
- assertThat(mKeyguardClockSwitch.switchToClock(LARGE)).isFalse();
+ assertThat(mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true)).isTrue();
+ assertThat(mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true)).isFalse();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 77762859de18..24b01e079b42 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -244,7 +244,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById(
R.id.keyguard_bouncer_user_switcher);
assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity)
- .isEqualTo(Gravity.LEFT | Gravity.TOP);
+ .isEqualTo(Gravity.LEFT | Gravity.CENTER_VERTICAL);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java
index 9a9b7c4f62eb..4a29ada8a998 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java
@@ -28,8 +28,8 @@ import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.communal.conditions.CommunalConditionsMonitor;
import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.condition.Monitor;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
@@ -45,7 +45,7 @@ public class CommunalManagerUpdaterTest extends SysuiTestCase {
@Mock
private CommunalManager mCommunalManager;
@Mock
- private CommunalConditionsMonitor mCommunalConditionsMonitor;
+ private Monitor mCommunalConditionsMonitor;
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@@ -55,7 +55,7 @@ public class CommunalManagerUpdaterTest extends SysuiTestCase {
mContext.addMockSystemService(CommunalManager.class, mCommunalManager);
doAnswer(invocation -> {
- final CommunalConditionsMonitor.Callback callback = invocation.getArgument(0);
+ final Monitor.Callback callback = invocation.getArgument(0);
callback.onConditionsChanged(true);
return null;
}).when(mCommunalConditionsMonitor).addCallback(any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java
index 409dd940ceb6..df1cc766d162 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java
@@ -31,8 +31,8 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.communal.conditions.CommunalConditionsMonitor;
import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.condition.Monitor;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
@@ -49,9 +49,9 @@ import java.lang.ref.WeakReference;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class CommunalSourceMonitorTest extends SysuiTestCase {
- @Mock private CommunalConditionsMonitor mCommunalConditionsMonitor;
+ @Mock private Monitor mCommunalConditionsMonitor;
- @Captor private ArgumentCaptor<CommunalConditionsMonitor.Callback> mConditionsCallbackCaptor;
+ @Captor private ArgumentCaptor<Monitor.Callback> mConditionsCallbackCaptor;
private CommunalSourceMonitor mCommunalSourceMonitor;
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@@ -156,7 +156,7 @@ public class CommunalSourceMonitorTest extends SysuiTestCase {
private void setConditionsMet(boolean value) {
mExecutor.runAllReady();
verify(mCommunalConditionsMonitor).addCallback(mConditionsCallbackCaptor.capture());
- final CommunalConditionsMonitor.Callback conditionsCallback =
+ final Monitor.Callback conditionsCallback =
mConditionsCallbackCaptor.getValue();
conditionsCallback.onConditionsChanged(value);
mExecutor.runAllReady();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
index 873f7a45571b..55af51d3fddf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
@@ -45,6 +45,8 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.unfold.FoldAodAnimationController;
+import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.util.wakelock.WakeLockFake;
import org.junit.After;
@@ -54,6 +56,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class DozeUiTest extends SysuiTestCase {
@@ -79,6 +83,10 @@ public class DozeUiTest extends SysuiTestCase {
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
+ private FoldAodAnimationController mFoldAodAnimationController;
+ @Mock
+ private SysUIUnfoldComponent mSysUIUnfoldComponent;
+ @Mock
private ConfigurationController mConfigurationController;
@Before
@@ -90,9 +98,13 @@ public class DozeUiTest extends SysuiTestCase {
mWakeLock = new WakeLockFake();
mHandler = mHandlerThread.getThreadHandler();
+ when(mSysUIUnfoldComponent.getFoldAodAnimationController())
+ .thenReturn(mFoldAodAnimationController);
+
mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler,
mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService,
- mStatusBarStateController, mConfigurationController);
+ mStatusBarStateController, Optional.of(mSysUIUnfoldComponent),
+ mConfigurationController);
mDozeUi.setDozeMachine(mMachine);
}
@@ -121,6 +133,7 @@ public class DozeUiTest extends SysuiTestCase {
reset(mHost);
when(mDozeParameters.getAlwaysOn()).thenReturn(false);
when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
+ when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(true);
mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false);
verify(mHost).setAnimateScreenOff(eq(false));
@@ -131,6 +144,7 @@ public class DozeUiTest extends SysuiTestCase {
reset(mHost);
when(mDozeParameters.getAlwaysOn()).thenReturn(true);
when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
+ when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(true);
// Take over when the keyguard is visible.
mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(true);
@@ -142,6 +156,18 @@ public class DozeUiTest extends SysuiTestCase {
}
@Test
+ public void propagatesAnimateScreenOff_alwaysOn_shouldAnimateDozingChangeIsFalse() {
+ reset(mHost);
+ when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+ when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
+ when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(false);
+
+ // Take over when the keyguard is visible.
+ mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(true);
+ verify(mHost).setAnimateScreenOff(eq(false));
+ }
+
+ @Test
public void neverAnimateScreenOff_whenNotSupported() {
// Re-initialize DozeParameters saying that the display requires blanking.
reset(mDozeParameters);
@@ -149,7 +175,8 @@ public class DozeUiTest extends SysuiTestCase {
when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true);
mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler,
mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService,
- mStatusBarStateController, mConfigurationController);
+ mStatusBarStateController, Optional.of(mSysUIUnfoldComponent),
+ mConfigurationController);
mDozeUi.setDozeMachine(mMachine);
// Never animate if display doesn't support it.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 53bfeee9135a..d0ec3f36427c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -37,6 +37,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -78,16 +79,41 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
@Mock
DreamOverlayStateController mDreamOverlayStateController;
+ @Mock
+ DreamOverlayComponent.Factory mDreamOverlayStatusBarViewComponentFactory;
+
+ @Mock
+ DreamOverlayComponent mDreamOverlayComponent;
+
+ @Mock
+ DreamOverlayStatusBarViewController mDreamOverlayStatusBarViewController;
+
+ @Mock
+ DreamOverlayContainerView mDreamOverlayContainerView;
+
+ @Mock
+ ViewGroup mDreamOverlayContentView;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext.addMockSystemService(WindowManager.class, mWindowManager);
+
+ when(mDreamOverlayComponent.getDreamOverlayContentView())
+ .thenReturn(mDreamOverlayContentView);
+ when(mDreamOverlayComponent.getDreamOverlayContainerView())
+ .thenReturn(mDreamOverlayContainerView);
+ when(mDreamOverlayComponent.getDreamOverlayStatusBarViewController())
+ .thenReturn(mDreamOverlayStatusBarViewController);
+ when(mDreamOverlayStatusBarViewComponentFactory.create())
+ .thenReturn(mDreamOverlayComponent);
+
}
@Test
public void testInteraction() throws Exception {
final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor,
- mDreamOverlayStateController);
+ mDreamOverlayStateController, mDreamOverlayStatusBarViewComponentFactory);
final IBinder proxy = service.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
clearInvocations(mWindowManager);
@@ -128,7 +154,7 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
@Test
public void testListening() throws Exception {
final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor,
- mDreamOverlayStateController);
+ mDreamOverlayStateController, mDreamOverlayStatusBarViewComponentFactory);
final IBinder proxy = service.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
@@ -150,4 +176,19 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
// Verify provider is asked to create overlay.
verify(mProvider).onCreateOverlay(any(), any(), any());
}
+
+ @Test
+ public void testDreamOverlayStatusBarViewControllerInitialized() throws Exception {
+ final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor,
+ mDreamOverlayStateController, mDreamOverlayStatusBarViewComponentFactory);
+
+ final IBinder proxy = service.onBind(new Intent());
+ final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+ // Inform the overlay service of dream starting.
+ overlay.startDream(mWindowParams, mDreamOverlayCallback);
+ mMainExecutor.runAllReady();
+
+ verify(mDreamOverlayStatusBarViewController).init();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
new file mode 100644
index 000000000000..7f72dda441d2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.battery.BatteryMeterViewController;
+import com.android.systemui.statusbar.policy.BatteryController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
+
+ @Mock
+ DreamOverlayStatusBarView mView;
+ @Mock
+ BatteryController mBatteryController;
+ @Mock
+ BatteryMeterViewController mBatteryMeterViewController;
+ @Mock
+ ConnectivityManager mConnectivityManager;
+ @Mock
+ NetworkCapabilities mNetworkCapabilities;
+ @Mock
+ Network mNetwork;
+
+ DreamOverlayStatusBarViewController mController;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mController = new DreamOverlayStatusBarViewController(
+ mContext, mView, mBatteryController, mBatteryMeterViewController,
+ mConnectivityManager);
+ }
+
+ @Test
+ public void testOnInitInitializesControllers() {
+ mController.onInit();
+ verify(mBatteryMeterViewController).init();
+ }
+
+ @Test
+ public void testOnViewAttachedAddsBatteryControllerCallback() {
+ mController.onViewAttached();
+ verify(mBatteryController)
+ .addCallback(any(BatteryController.BatteryStateChangeCallback.class));
+ }
+
+ @Test
+ public void testOnViewAttachedRegistersNetworkCallback() {
+ mController.onViewAttached();
+ verify(mConnectivityManager)
+ .registerNetworkCallback(any(NetworkRequest.class), any(
+ ConnectivityManager.NetworkCallback.class));
+ }
+
+ @Test
+ public void testOnViewAttachedShowsWifiStatusWhenWifiUnavailable() {
+ when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+ .thenReturn(false);
+ when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
+ mController.onViewAttached();
+ verify(mView).showWifiStatus(true);
+ }
+
+ @Test
+ public void testOnViewAttachedHidesWifiStatusWhenWifiAvailable() {
+ when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+ .thenReturn(true);
+ when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
+ mController.onViewAttached();
+ verify(mView).showWifiStatus(false);
+ }
+
+ @Test
+ public void testOnViewAttachedShowsWifiStatusWhenNetworkCapabilitiesUnavailable() {
+ when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(null);
+ mController.onViewAttached();
+ verify(mView).showWifiStatus(true);
+ }
+
+ @Test
+ public void testOnViewDetachedRemovesBatteryControllerCallback() {
+ mController.onViewDetached();
+ verify(mBatteryController)
+ .removeCallback(any(BatteryController.BatteryStateChangeCallback.class));
+ }
+
+ @Test
+ public void testOnViewDetachedUnregistersNetworkCallback() {
+ mController.onViewDetached();
+ verify(mConnectivityManager)
+ .unregisterNetworkCallback(any(ConnectivityManager.NetworkCallback.class));
+ }
+
+ @Test
+ public void testBatteryPercentTextShownWhenBatteryLevelChangesWhileCharging() {
+ final ArgumentCaptor<BatteryController.BatteryStateChangeCallback> callbackCapture =
+ ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback.class);
+ mController.onViewAttached();
+ verify(mBatteryController).addCallback(callbackCapture.capture());
+ callbackCapture.getValue().onBatteryLevelChanged(1, true, true);
+ verify(mView).showBatteryPercentText(true);
+ }
+
+ @Test
+ public void testBatteryPercentTextHiddenWhenBatteryLevelChangesWhileNotCharging() {
+ final ArgumentCaptor<BatteryController.BatteryStateChangeCallback> callbackCapture =
+ ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback.class);
+ mController.onViewAttached();
+ verify(mBatteryController).addCallback(callbackCapture.capture());
+ callbackCapture.getValue().onBatteryLevelChanged(1, true, false);
+ verify(mView).showBatteryPercentText(false);
+ }
+
+ @Test
+ public void testWifiStatusHiddenWhenWifiBecomesAvailable() {
+ // Make sure wifi starts out unavailable when onViewAttached is called.
+ when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+ .thenReturn(false);
+ when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
+ mController.onViewAttached();
+
+ final ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackCapture =
+ ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+ verify(mConnectivityManager).registerNetworkCallback(any(), callbackCapture.capture());
+ callbackCapture.getValue().onAvailable(mNetwork);
+ verify(mView).showWifiStatus(false);
+ }
+
+ @Test
+ public void testWifiStatusShownWhenWifiBecomesUnavailable() {
+ // Make sure wifi starts out available when onViewAttached is called.
+ when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+ .thenReturn(true);
+ when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
+ mController.onViewAttached();
+
+ final ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackCapture =
+ ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+ verify(mConnectivityManager).registerNetworkCallback(any(), callbackCapture.capture());
+ callbackCapture.getValue().onLost(mNetwork);
+ verify(mView).showWifiStatus(true);
+ }
+
+ @Test
+ public void testWifiStatusHiddenWhenCapabilitiesChange() {
+ // Make sure wifi starts out unavailable when onViewAttached is called.
+ when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+ .thenReturn(false);
+ when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
+ mController.onViewAttached();
+
+ final ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackCapture =
+ ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+ verify(mConnectivityManager).registerNetworkCallback(any(), callbackCapture.capture());
+ when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+ .thenReturn(true);
+ callbackCapture.getValue().onCapabilitiesChanged(mNetwork, mNetworkCapabilities);
+ verify(mView).showWifiStatus(false);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 8d329c56af36..27fcb11b8dfc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -58,6 +58,7 @@ import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.unfold.FoldAodAnimationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
import com.android.systemui.util.DeviceConfigProxy;
@@ -69,7 +70,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -95,62 +95,40 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private @Mock NavigationModeController mNavigationModeController;
private @Mock KeyguardDisplayManager mKeyguardDisplayManager;
private @Mock DozeParameters mDozeParameters;
- private @Mock Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent;
- private @Mock Optional<UnfoldLightRevealOverlayAnimation> mUnfoldAnimationOptional;
+ private @Mock SysUIUnfoldComponent mSysUIUnfoldComponent;
private @Mock UnfoldLightRevealOverlayAnimation mUnfoldAnimation;
private @Mock SysuiStatusBarStateController mStatusBarStateController;
private @Mock KeyguardStateController mKeyguardStateController;
private @Mock NotificationShadeDepthController mNotificationShadeDepthController;
private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private @Mock ScreenOffAnimationController mScreenOffAnimationController;
+ private @Mock FoldAodAnimationController mFoldAodAnimationController;
private @Mock IKeyguardDrawnCallback mKeyguardDrawnCallback;
private @Mock InteractionJankMonitor mInteractionJankMonitor;
private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
+ private Optional<SysUIUnfoldComponent> mSysUiUnfoldComponentOptional;
+
private FalsingCollectorFake mFalsingCollector;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mFalsingCollector = new FalsingCollectorFake();
+ mSysUiUnfoldComponentOptional = Optional.of(mSysUIUnfoldComponent);
when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class));
- when(mSysUIUnfoldComponent.map(
- ArgumentMatchers.<Function<SysUIUnfoldComponent, UnfoldLightRevealOverlayAnimation>>
- any()))
- .thenReturn(mUnfoldAnimationOptional);
- when(mUnfoldAnimationOptional.isPresent()).thenReturn(true);
- when(mUnfoldAnimationOptional.get()).thenReturn(mUnfoldAnimation);
+ when(mSysUIUnfoldComponent.getUnfoldLightRevealOverlayAnimation())
+ .thenReturn(mUnfoldAnimation);
+ when(mSysUIUnfoldComponent.getFoldAodAnimationController())
+ .thenReturn(mFoldAodAnimationController);
+
when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true);
when(mInteractionJankMonitor.end(anyInt())).thenReturn(true);
- mViewMediator = new KeyguardViewMediator(
- mContext,
- mFalsingCollector,
- mLockPatternUtils,
- mBroadcastDispatcher,
- () -> mStatusBarKeyguardViewManager,
- mDismissCallbackRegistry,
- mUpdateMonitor,
- mDumpManager,
- mUiBgExecutor,
- mPowerManager,
- mTrustManager,
- mUserSwitcherController,
- mDeviceConfig,
- mNavigationModeController,
- mKeyguardDisplayManager,
- mDozeParameters,
- mSysUIUnfoldComponent,
- mStatusBarStateController,
- mKeyguardStateController,
- () -> mKeyguardUnlockAnimationController,
- mScreenOffAnimationController,
- () -> mNotificationShadeDepthController,
- mInteractionJankMonitor);
- mViewMediator.start();
+ createAndStartViewMediator();
}
@Test
@@ -179,6 +157,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mViewMediator.onScreenTurningOn(mKeyguardDrawnCallback);
TestableLooper.get(this).processAllMessages();
onUnfoldOverlayReady();
+ onFoldAodReady();
// Should be called when both unfold overlay and keyguard drawn ready
verify(mKeyguardDrawnCallback).onDrawn();
@@ -188,7 +167,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public void testUnfoldTransitionDisabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback()
throws RemoteException {
- when(mUnfoldAnimationOptional.isPresent()).thenReturn(false);
+ mSysUiUnfoldComponentOptional = Optional.empty();
+ createAndStartViewMediator();
mViewMediator.onScreenTurningOn(mKeyguardDrawnCallback);
TestableLooper.get(this).processAllMessages();
@@ -200,6 +180,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
@Test
public void testIsAnimatingScreenOff() {
when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
+ when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(true);
mViewMediator.onFinishedGoingToSleep(OFF_BECAUSE_OF_USER, false);
mViewMediator.setDozing(true);
@@ -244,4 +225,39 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
overlayReadyCaptor.getValue().run();
TestableLooper.get(this).processAllMessages();
}
+
+ private void onFoldAodReady() {
+ ArgumentCaptor<Runnable> ready = ArgumentCaptor.forClass(Runnable.class);
+ verify(mFoldAodAnimationController).onScreenTurningOn(ready.capture());
+ ready.getValue().run();
+ TestableLooper.get(this).processAllMessages();
+ }
+
+ private void createAndStartViewMediator() {
+ mViewMediator = new KeyguardViewMediator(
+ mContext,
+ mFalsingCollector,
+ mLockPatternUtils,
+ mBroadcastDispatcher,
+ () -> mStatusBarKeyguardViewManager,
+ mDismissCallbackRegistry,
+ mUpdateMonitor,
+ mDumpManager,
+ mUiBgExecutor,
+ mPowerManager,
+ mTrustManager,
+ mUserSwitcherController,
+ mDeviceConfig,
+ mNavigationModeController,
+ mKeyguardDisplayManager,
+ mDozeParameters,
+ mSysUiUnfoldComponentOptional,
+ mStatusBarStateController,
+ mKeyguardStateController,
+ () -> mKeyguardUnlockAnimationController,
+ mScreenOffAnimationController,
+ () -> mNotificationShadeDepthController,
+ mInteractionJankMonitor);
+ mViewMediator.start();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
new file mode 100644
index 000000000000..af33dafb6107
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tileimpl
+
+import android.content.ComponentName
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.external.CustomTile
+import com.android.systemui.qs.tiles.AirplaneModeTile
+import com.android.systemui.qs.tiles.AlarmTile
+import com.android.systemui.qs.tiles.BatterySaverTile
+import com.android.systemui.qs.tiles.BluetoothTile
+import com.android.systemui.qs.tiles.CameraToggleTile
+import com.android.systemui.qs.tiles.CastTile
+import com.android.systemui.qs.tiles.CellularTile
+import com.android.systemui.qs.tiles.ColorInversionTile
+import com.android.systemui.qs.tiles.DataSaverTile
+import com.android.systemui.qs.tiles.DeviceControlsTile
+import com.android.systemui.qs.tiles.DndTile
+import com.android.systemui.qs.tiles.FgsManagerTile
+import com.android.systemui.qs.tiles.FlashlightTile
+import com.android.systemui.qs.tiles.HotspotTile
+import com.android.systemui.qs.tiles.InternetTile
+import com.android.systemui.qs.tiles.LocationTile
+import com.android.systemui.qs.tiles.MicrophoneToggleTile
+import com.android.systemui.qs.tiles.NfcTile
+import com.android.systemui.qs.tiles.NightDisplayTile
+import com.android.systemui.qs.tiles.OneHandedModeTile
+import com.android.systemui.qs.tiles.QRCodeScannerTile
+import com.android.systemui.qs.tiles.QuickAccessWalletTile
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+import com.android.systemui.qs.tiles.RotationLockTile
+import com.android.systemui.qs.tiles.ScreenRecordTile
+import com.android.systemui.qs.tiles.UiModeNightTile
+import com.android.systemui.qs.tiles.UserTile
+import com.android.systemui.qs.tiles.WifiTile
+import com.android.systemui.qs.tiles.WorkModeTile
+import com.android.systemui.util.leak.GarbageMonitor
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
+
+private val specMap = mapOf(
+ "wifi" to WifiTile::class.java,
+ "internet" to InternetTile::class.java,
+ "bt" to BluetoothTile::class.java,
+ "cell" to CellularTile::class.java,
+ "dnd" to DndTile::class.java,
+ "inversion" to ColorInversionTile::class.java,
+ "airplane" to AirplaneModeTile::class.java,
+ "work" to WorkModeTile::class.java,
+ "rotation" to RotationLockTile::class.java,
+ "flashlight" to FlashlightTile::class.java,
+ "location" to LocationTile::class.java,
+ "cast" to CastTile::class.java,
+ "hotspot" to HotspotTile::class.java,
+ "user" to UserTile::class.java,
+ "battery" to BatterySaverTile::class.java,
+ "saver" to DataSaverTile::class.java,
+ "night" to NightDisplayTile::class.java,
+ "nfc" to NfcTile::class.java,
+ "dark" to UiModeNightTile::class.java,
+ "screenrecord" to ScreenRecordTile::class.java,
+ "reduce_brightness" to ReduceBrightColorsTile::class.java,
+ "cameratoggle" to CameraToggleTile::class.java,
+ "mictoggle" to MicrophoneToggleTile::class.java,
+ "controls" to DeviceControlsTile::class.java,
+ "alarm" to AlarmTile::class.java,
+ "wallet" to QuickAccessWalletTile::class.java,
+ "qr_code_scanner" to QRCodeScannerTile::class.java,
+ "onehanded" to OneHandedModeTile::class.java,
+ "fgsmanager" to FgsManagerTile::class.java
+)
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class QSFactoryImplTest : SysuiTestCase() {
+
+ @Mock private lateinit var qsHost: QSHost
+ @Mock(answer = Answers.RETURNS_SELF) private lateinit var customTileBuilder: CustomTile.Builder
+ @Mock private lateinit var customTile: CustomTile
+
+ @Mock private lateinit var wifiTile: WifiTile
+ @Mock private lateinit var internetTile: InternetTile
+ @Mock private lateinit var bluetoothTile: BluetoothTile
+ @Mock private lateinit var cellularTile: CellularTile
+ @Mock private lateinit var dndTile: DndTile
+ @Mock private lateinit var colorInversionTile: ColorInversionTile
+ @Mock private lateinit var airplaneTile: AirplaneModeTile
+ @Mock private lateinit var workTile: WorkModeTile
+ @Mock private lateinit var rotationTile: RotationLockTile
+ @Mock private lateinit var flashlightTile: FlashlightTile
+ @Mock private lateinit var locationTile: LocationTile
+ @Mock private lateinit var castTile: CastTile
+ @Mock private lateinit var hotspotTile: HotspotTile
+ @Mock private lateinit var userTile: UserTile
+ @Mock private lateinit var batterySaverTile: BatterySaverTile
+ @Mock private lateinit var dataSaverTile: DataSaverTile
+ @Mock private lateinit var nightDisplayTile: NightDisplayTile
+ @Mock private lateinit var nfcTile: NfcTile
+ @Mock private lateinit var memoryTile: GarbageMonitor.MemoryTile
+ @Mock private lateinit var darkModeTile: UiModeNightTile
+ @Mock private lateinit var screenRecordTile: ScreenRecordTile
+ @Mock private lateinit var reduceBrightColorsTile: ReduceBrightColorsTile
+ @Mock private lateinit var cameraToggleTile: CameraToggleTile
+ @Mock private lateinit var microphoneToggleTile: MicrophoneToggleTile
+ @Mock private lateinit var deviceControlsTile: DeviceControlsTile
+ @Mock private lateinit var alarmTile: AlarmTile
+ @Mock private lateinit var quickAccessWalletTile: QuickAccessWalletTile
+ @Mock private lateinit var qrCodeScannerTile: QRCodeScannerTile
+ @Mock private lateinit var oneHandedModeTile: OneHandedModeTile
+ @Mock private lateinit var fgsManagerTile: FgsManagerTile
+
+ private lateinit var factory: QSFactoryImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(qsHost.context).thenReturn(mContext)
+ whenever(qsHost.userContext).thenReturn(mContext)
+ whenever(customTileBuilder.build()).thenReturn(customTile)
+
+ factory = QSFactoryImpl(
+ { qsHost },
+ { customTileBuilder },
+ { wifiTile },
+ { internetTile },
+ { bluetoothTile },
+ { cellularTile },
+ { dndTile },
+ { colorInversionTile },
+ { airplaneTile },
+ { workTile },
+ { rotationTile },
+ { flashlightTile },
+ { locationTile },
+ { castTile },
+ { hotspotTile },
+ { userTile },
+ { batterySaverTile },
+ { dataSaverTile },
+ { nightDisplayTile },
+ { nfcTile },
+ { memoryTile },
+ { darkModeTile },
+ { screenRecordTile },
+ { reduceBrightColorsTile },
+ { cameraToggleTile },
+ { microphoneToggleTile },
+ { deviceControlsTile },
+ { alarmTile },
+ { quickAccessWalletTile },
+ { qrCodeScannerTile },
+ { oneHandedModeTile },
+ { fgsManagerTile }
+ )
+ // When adding/removing tiles, fix also [specMap]
+ }
+
+ @Test
+ fun testCorrectTileClassStock() {
+ specMap.forEach { spec, klazz ->
+ assertThat(factory.createTile(spec)).isInstanceOf(klazz)
+ }
+ }
+
+ @Test
+ fun testCustomTileClass() {
+ val customSpec = CustomTile.toSpec(ComponentName("test", "test"))
+ assertThat(factory.createTile(customSpec)).isInstanceOf(CustomTile::class.java)
+ }
+
+ @Test
+ fun testBadTileNull() {
+ assertThat(factory.createTile("-432~")).isNull()
+ }
+
+ @Test
+ fun testTileInitializedAndStale() {
+ specMap.forEach { spec, _ ->
+ val tile = factory.createTile(spec) as QSTileImpl<*>
+ val inOrder = inOrder(tile)
+ inOrder.verify(tile).initialize()
+ inOrder.verify(tile).postStale()
+ }
+
+ val customSpec = CustomTile.toSpec(ComponentName("test", "test"))
+ val tile = factory.createTile(customSpec) as QSTileImpl<*>
+ val inOrder = inOrder(tile)
+ inOrder.verify(tile).initialize()
+ inOrder.verify(tile).postStale()
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 9bcdcc96767a..1cd9b9e77423 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -425,6 +425,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
.thenReturn(mKeyguardUserSwitcherComponent);
when(mKeyguardUserSwitcherComponent.getKeyguardUserSwitcherController())
.thenReturn(mKeyguardUserSwitcherController);
+ when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(true);
doAnswer((Answer<Void>) invocation -> {
mTouchHandler = invocation.getArgument(0);
@@ -880,11 +881,11 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
triggerPositionClockAndNotifications();
- verify(mKeyguardStatusViewController).displayClock(LARGE);
+ verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
mNotificationPanelViewController.closeQs();
- verify(mKeyguardStatusViewController).displayClock(SMALL);
+ verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true);
}
@Test
@@ -894,12 +895,14 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
triggerPositionClockAndNotifications();
- verify(mKeyguardStatusViewController).displayClock(LARGE);
+ verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
triggerPositionClockAndNotifications();
- verify(mKeyguardStatusViewController, times(2)).displayClock(LARGE);
- verify(mKeyguardStatusViewController, never()).displayClock(SMALL);
+ verify(mKeyguardStatusViewController, times(2))
+ .displayClock(LARGE, /* animate */ true);
+ verify(mKeyguardStatusViewController, never())
+ .displayClock(SMALL, /* animate */ true);
}
@Test
@@ -911,7 +914,20 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mNotificationPanelViewController.setDozing(true, false, null);
- verify(mKeyguardStatusViewController).displayClock(LARGE);
+ verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
+ }
+
+ @Test
+ public void testSwitchesToBigClockInSplitShadeOnAodAnimateDisabled() {
+ when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false);
+ mStatusBarStateController.setState(KEYGUARD);
+ enableSplitShade(/* enabled= */ true);
+ when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+
+ mNotificationPanelViewController.setDozing(true, false, null);
+
+ verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ false);
}
@Test
@@ -923,13 +939,13 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
// one notification + media player visible
when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
triggerPositionClockAndNotifications();
- verify(mKeyguardStatusViewController).displayClock(SMALL);
+ verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true);
// only media player visible
when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
triggerPositionClockAndNotifications();
- verify(mKeyguardStatusViewController, times(2)).displayClock(SMALL);
- verify(mKeyguardStatusViewController, never()).displayClock(LARGE);
+ verify(mKeyguardStatusViewController, times(2)).displayClock(SMALL, true);
+ verify(mKeyguardStatusViewController, never()).displayClock(LARGE, /* animate */ true);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt
index 2d548e96e598..a8544a9a15e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt
@@ -1,8 +1,8 @@
package com.android.systemui.statusbar.phone
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.view.View
+import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ShadeInterpolation
@@ -42,6 +42,7 @@ class SplitShadeHeaderControllerTest : SysuiTestCase() {
var viewVisibility = View.GONE
private lateinit var splitShadeHeaderController: SplitShadeHeaderController
+ private lateinit var carrierIconSlots: List<String>
@Before
fun setup() {
@@ -67,12 +68,13 @@ class SplitShadeHeaderControllerTest : SysuiTestCase() {
featureFlags,
batteryMeterViewController
)
+ carrierIconSlots = listOf(
+ context.getString(com.android.internal.R.string.status_bar_mobile))
}
@Test
fun setVisible_onlyInSplitShade() {
- splitShadeHeaderController.splitShadeMode = true
- splitShadeHeaderController.shadeExpanded = true
+ makeShadeVisible()
assertThat(viewVisibility).isEqualTo(View.VISIBLE)
splitShadeHeaderController.splitShadeMode = false
@@ -81,17 +83,38 @@ class SplitShadeHeaderControllerTest : SysuiTestCase() {
@Test
fun updateListeners_registersWhenVisible() {
- splitShadeHeaderController.splitShadeMode = true
- splitShadeHeaderController.shadeExpanded = true
+ makeShadeVisible()
verify(qsCarrierGroupController).setListening(true)
verify(statusBarIconController).addIconGroup(any())
}
@Test
fun shadeExpandedFraction_updatesAlpha() {
- splitShadeHeaderController.splitShadeMode = true
- splitShadeHeaderController.shadeExpanded = true
+ makeShadeVisible()
splitShadeHeaderController.shadeExpandedFraction = 0.5f
verify(view).setAlpha(ShadeInterpolation.getContentAlpha(0.5f))
}
-} \ No newline at end of file
+
+ @Test
+ fun singleCarrier_enablesCarrierIconsInStatusIcons() {
+ whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(true)
+
+ makeShadeVisible()
+
+ verify(statusIcons).removeIgnoredSlots(carrierIconSlots)
+ }
+
+ @Test
+ fun dualCarrier_disablesCarrierIconsInStatusIcons() {
+ whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(false)
+
+ makeShadeVisible()
+
+ verify(statusIcons).addIgnoredSlots(carrierIconSlots)
+ }
+
+ private fun makeShadeVisible() {
+ splitShadeHeaderController.splitShadeMode = true
+ splitShadeHeaderController.shadeExpanded = true
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index c5bdfed6082b..cbaa460d711b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -43,9 +43,6 @@ import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dock.DockManager;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.DismissCallbackRegistry;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -79,9 +76,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Mock
private NotificationPanelViewController mNotificationPanelView;
@Mock
- private BiometricUnlockController mBiometrucUnlockController;
- @Mock
- private DismissCallbackRegistry mDismissCallbackRegistry;
+ private BiometricUnlockController mBiometricUnlockController;
@Mock
private SysuiStatusBarStateController mStatusBarStateController;
@Mock
@@ -97,15 +92,12 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Mock
private KeyguardBouncer mBouncer;
@Mock
- private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
- @Mock
private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAlternateAuthInterceptor;
@Mock
private KeyguardMessageArea mKeyguardMessageArea;
@Mock
private ShadeController mShadeController;
- private WakefulnessLifecycle mWakefulnessLifecycle;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Before
@@ -117,10 +109,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
.thenReturn(mBouncer);
when(mStatusBar.getBouncerContainer()).thenReturn(mContainer);
when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
- mWakefulnessLifecycle = new WakefulnessLifecycle(
- getContext(),
- null,
- mock(DumpManager.class));
mStatusBarKeyguardViewManager = new StatusBarKeyguardViewManager(
getContext(),
mViewMediatorCallback,
@@ -134,15 +122,13 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mKeyguardStateController,
mock(NotificationMediaManager.class),
mKeyguardBouncerFactory,
- mWakefulnessLifecycle,
- mUnlockedScreenOffAnimationController,
mKeyguardMessageAreaFactory,
() -> mShadeController);
mStatusBarKeyguardViewManager.registerStatusBar(
mStatusBar,
mNotificationPanelView,
new PanelExpansionStateManager(),
- mBiometrucUnlockController,
+ mBiometricUnlockController,
mNotificationContainer,
mBypassController);
mStatusBarKeyguardViewManager.show(null);
@@ -261,7 +247,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Test
public void onPanelExpansionChanged_neverTranslatesBouncerWhenWakeAndUnlock() {
- when(mBiometrucUnlockController.getMode())
+ when(mBiometricUnlockController.getMode())
.thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(
/* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
index 878bdeac43c9..d6454490e62a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
@@ -58,7 +58,7 @@ public class ConditionMonitorTest extends SysuiTestCase {
mCondition3 = spy(new FakeCondition());
mConditions = new HashSet<>(Arrays.asList(mCondition1, mCondition2, mCondition3));
- mConditionMonitor = new Monitor(mConditions);
+ mConditionMonitor = new Monitor(mConditions, null /*callbacks*/);
}
@Test
@@ -98,7 +98,7 @@ public class ConditionMonitorTest extends SysuiTestCase {
@Test
public void addCallback_noConditions_reportAllConditionsMet() {
- final Monitor monitor = new Monitor(new HashSet<>());
+ final Monitor monitor = new Monitor(new HashSet<>(), null /*callbacks*/);
final Monitor.Callback callback = mock(Monitor.Callback.class);
monitor.addCallback(callback);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 1e15d2ae0bdb..2f2e536322db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -35,6 +35,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.compatui.CompatUI;
import com.android.wm.shell.draganddrop.DragAndDrop;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
@@ -42,7 +43,6 @@ import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
import com.android.wm.shell.splitscreen.SplitScreen;
import org.junit.Before;
@@ -78,7 +78,7 @@ public class WMShellTest extends SysuiTestCase {
@Mock WakefulnessLifecycle mWakefulnessLifecycle;
@Mock ProtoTracer mProtoTracer;
@Mock ShellCommandHandler mShellCommandHandler;
- @Mock SizeCompatUI mSizeCompatUI;
+ @Mock CompatUI mCompatUI;
@Mock ShellExecutor mSysUiMainExecutor;
@Mock DragAndDrop mDragAndDrop;
@@ -88,7 +88,7 @@ public class WMShellTest extends SysuiTestCase {
mWMShell = new WMShell(mContext, Optional.of(mPip), Optional.of(mLegacySplitScreen),
Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mHideDisplayCutout),
- Optional.of(mShellCommandHandler), Optional.of(mSizeCompatUI),
+ Optional.of(mShellCommandHandler), Optional.of(mCompatUI),
Optional.of(mDragAndDrop),
mCommandQueue, mConfigurationController, mKeyguardUpdateMonitor,
mNavigationModeController, mScreenLifecycle, mSysUiState, mProtoTracer,
@@ -136,8 +136,8 @@ public class WMShellTest extends SysuiTestCase {
}
@Test
- public void initSizeCompatUI_registersCallbacks() {
- mWMShell.initSizeCompatUi(mSizeCompatUI);
+ public void initCompatUI_registersCallbacks() {
+ mWMShell.initCompatUi(mCompatUI);
verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
}
diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values-land/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values-land/dimens.xml
index 1f5834d72dd0..a03dcbd3941d 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlay/res/values-land/dimens.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values-land/dimens.xml
@@ -18,5 +18,5 @@
-->
<resources>
<!-- The height of the bottom navigation gesture area. -->
- <dimen name="navigation_bar_gesture_height">32dp</dimen>
+ <dimen name="navigation_bar_gesture_height">24dp</dimen>
</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml
index ac1f0226be52..c5d0c9e3f348 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml
@@ -18,13 +18,13 @@
-->
<resources>
<!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_height">16dp</dimen>
+ <dimen name="navigation_bar_height">24dp</dimen>
<!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
- <dimen name="navigation_bar_height_landscape">16dp</dimen>
+ <dimen name="navigation_bar_height_landscape">24dp</dimen>
<!-- Width of the navigation bar when it is placed vertically on the screen -->
- <dimen name="navigation_bar_width">16dp</dimen>
+ <dimen name="navigation_bar_width">24dp</dimen>
<!-- Height of the bottom navigation / system bar. -->
<dimen name="navigation_bar_frame_height">48dp</dimen>
<!-- The height of the bottom navigation gesture area. -->
- <dimen name="navigation_bar_gesture_height">32dp</dimen>
+ <dimen name="navigation_bar_gesture_height">24dp</dimen>
</resources> \ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
index ac1f0226be52..c5d0c9e3f348 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
@@ -18,13 +18,13 @@
-->
<resources>
<!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_height">16dp</dimen>
+ <dimen name="navigation_bar_height">24dp</dimen>
<!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
- <dimen name="navigation_bar_height_landscape">16dp</dimen>
+ <dimen name="navigation_bar_height_landscape">24dp</dimen>
<!-- Width of the navigation bar when it is placed vertically on the screen -->
- <dimen name="navigation_bar_width">16dp</dimen>
+ <dimen name="navigation_bar_width">24dp</dimen>
<!-- Height of the bottom navigation / system bar. -->
<dimen name="navigation_bar_frame_height">48dp</dimen>
<!-- The height of the bottom navigation gesture area. -->
- <dimen name="navigation_bar_gesture_height">32dp</dimen>
+ <dimen name="navigation_bar_gesture_height">24dp</dimen>
</resources> \ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
index ac1f0226be52..c5d0c9e3f348 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
@@ -18,13 +18,13 @@
-->
<resources>
<!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_height">16dp</dimen>
+ <dimen name="navigation_bar_height">24dp</dimen>
<!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
- <dimen name="navigation_bar_height_landscape">16dp</dimen>
+ <dimen name="navigation_bar_height_landscape">24dp</dimen>
<!-- Width of the navigation bar when it is placed vertically on the screen -->
- <dimen name="navigation_bar_width">16dp</dimen>
+ <dimen name="navigation_bar_width">24dp</dimen>
<!-- Height of the bottom navigation / system bar. -->
<dimen name="navigation_bar_frame_height">48dp</dimen>
<!-- The height of the bottom navigation gesture area. -->
- <dimen name="navigation_bar_gesture_height">32dp</dimen>
+ <dimen name="navigation_bar_gesture_height">24dp</dimen>
</resources> \ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
index ac1f0226be52..c5d0c9e3f348 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
@@ -18,13 +18,13 @@
-->
<resources>
<!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_height">16dp</dimen>
+ <dimen name="navigation_bar_height">24dp</dimen>
<!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
- <dimen name="navigation_bar_height_landscape">16dp</dimen>
+ <dimen name="navigation_bar_height_landscape">24dp</dimen>
<!-- Width of the navigation bar when it is placed vertically on the screen -->
- <dimen name="navigation_bar_width">16dp</dimen>
+ <dimen name="navigation_bar_width">24dp</dimen>
<!-- Height of the bottom navigation / system bar. -->
<dimen name="navigation_bar_frame_height">48dp</dimen>
<!-- The height of the bottom navigation gesture area. -->
- <dimen name="navigation_bar_gesture_height">32dp</dimen>
+ <dimen name="navigation_bar_gesture_height">24dp</dimen>
</resources> \ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 881c910b399b..f050b6622a5d 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -1027,8 +1027,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
mSystemSupport.getMagnificationProcessor();
final long identity = Binder.clearCallingIdentity();
try {
- magnificationProcessor.getMagnificationRegion(displayId, region,
- mSecurityPolicy.canControlMagnification(this));
+ magnificationProcessor.getFullscreenMagnificationRegion(displayId,
+ region, mSecurityPolicy.canControlMagnification(this));
return region;
} finally {
Binder.restoreCallingIdentity(identity);
@@ -1095,7 +1095,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
try {
MagnificationProcessor magnificationProcessor =
mSystemSupport.getMagnificationProcessor();
- return (magnificationProcessor.reset(displayId, animate)
+ return (magnificationProcessor.resetFullscreenMagnification(displayId, animate)
|| !magnificationProcessor.isMagnifying(displayId));
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
index d0b989582ebe..7a525ee23d27 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -119,6 +119,18 @@ public class MagnificationProcessor {
return false;
}
+ private boolean setScaleAndCenterForFullScreenMagnification(int displayId, float scale,
+ float centerX, float centerY,
+ boolean animate, int id) {
+ if (!isRegistered(displayId)) {
+ register(displayId);
+ }
+ return mController.getFullScreenMagnificationController().setScaleAndCenter(
+ displayId,
+ scale,
+ centerX, centerY, animate, id);
+ }
+
/**
* Returns {@code true} if transition magnification mode needed. And it is no need to transition
* mode when the controlling mode is unchanged or the controlling magnifier is not activated.
@@ -135,24 +147,18 @@ public class MagnificationProcessor {
}
/**
- * Returns the magnification scale. If an animation is in progress,
- * this reflects the end state of the animation.
+ * Returns the magnification scale of full-screen magnification on the display.
+ * If an animation is in progress, this reflects the end state of the animation.
*
* @param displayId The logical display id.
* @return the scale
*/
public float getScale(int displayId) {
- int mode = getControllingMode(displayId);
- if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
- return mController.getFullScreenMagnificationController().getScale(displayId);
- } else if (mode == MAGNIFICATION_MODE_WINDOW) {
- return mController.getWindowMagnificationMgr().getScale(displayId);
- }
- return 0;
+ return mController.getFullScreenMagnificationController().getScale(displayId);
}
/**
- * Returns the magnification center in X coordinate of the controlling magnification mode.
+ * Returns the magnification center in X coordinate of full-screen magnification.
* If the service can control magnification but fullscreen magnifier is not registered, it will
* register the magnifier for this call then unregister the magnifier finally to make the
* magnification center correct.
@@ -162,25 +168,19 @@ public class MagnificationProcessor {
* @return the X coordinate
*/
public float getCenterX(int displayId, boolean canControlMagnification) {
- int mode = getControllingMode(displayId);
- if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
- boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
- canControlMagnification);
- try {
- return mController.getFullScreenMagnificationController().getCenterX(displayId);
- } finally {
- if (registeredJustForThisCall) {
- unregister(displayId);
- }
+ boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
+ canControlMagnification);
+ try {
+ return mController.getFullScreenMagnificationController().getCenterX(displayId);
+ } finally {
+ if (registeredJustForThisCall) {
+ unregister(displayId);
}
- } else if (mode == MAGNIFICATION_MODE_WINDOW) {
- return mController.getWindowMagnificationMgr().getCenterX(displayId);
}
- return 0;
}
/**
- * Returns the magnification center in Y coordinate of the controlling magnification mode.
+ * Returns the magnification center in Y coordinate of full-screen magnification.
* If the service can control magnification but fullscreen magnifier is not registered, it will
* register the magnifier for this call then unregister the magnifier finally to make the
* magnification center correct.
@@ -190,49 +190,25 @@ public class MagnificationProcessor {
* @return the Y coordinate
*/
public float getCenterY(int displayId, boolean canControlMagnification) {
- int mode = getControllingMode(displayId);
- if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
- boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
- canControlMagnification);
- try {
- return mController.getFullScreenMagnificationController().getCenterY(displayId);
- } finally {
- if (registeredJustForThisCall) {
- unregister(displayId);
- }
+ boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
+ canControlMagnification);
+ try {
+ return mController.getFullScreenMagnificationController().getCenterY(displayId);
+ } finally {
+ if (registeredJustForThisCall) {
+ unregister(displayId);
}
- } else if (mode == MAGNIFICATION_MODE_WINDOW) {
- return mController.getWindowMagnificationMgr().getCenterY(displayId);
}
- return 0;
}
/**
- * Return the magnification bounds of the current controlling magnification on the given
- * display. If the magnifier is not enabled, it returns an empty region.
- * If the service can control magnification but fullscreen magnifier is not registered, it will
- * register the magnifier for this call then unregister the magnifier finally to make
- * the magnification region correct.
+ * Returns the magnification bounds of full-screen magnification on the given display.
*
* @param displayId The logical display id
* @param outRegion the region to populate
* @param canControlMagnification Whether the service can control magnification
- * @return outRegion the magnification bounds of full-screen magnifier or the magnification
- * source bounds of window magnifier
*/
- public Region getMagnificationRegion(int displayId, @NonNull Region outRegion,
- boolean canControlMagnification) {
- int mode = getControllingMode(displayId);
- if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
- getFullscreenMagnificationRegion(displayId, outRegion, canControlMagnification);
- } else if (mode == MAGNIFICATION_MODE_WINDOW) {
- mController.getWindowMagnificationMgr().getMagnificationSourceBounds(displayId,
- outRegion);
- }
- return outRegion;
- }
-
- private void getFullscreenMagnificationRegion(int displayId, @NonNull Region outRegion,
+ public void getFullscreenMagnificationRegion(int displayId, @NonNull Region outRegion,
boolean canControlMagnification) {
boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
canControlMagnification);
@@ -246,21 +222,9 @@ public class MagnificationProcessor {
}
}
- private boolean setScaleAndCenterForFullScreenMagnification(int displayId, float scale,
- float centerX, float centerY,
- boolean animate, int id) {
- if (!isRegistered(displayId)) {
- register(displayId);
- }
- return mController.getFullScreenMagnificationController().setScaleAndCenter(
- displayId,
- scale,
- centerX, centerY, animate, id);
- }
-
/**
- * Resets the magnification on the given display. The reset mode could be full-screen or
- * window if it is activated.
+ * Resets the current magnification on the given display. The reset mode could be
+ * full-screen or window if it is activated.
*
* @param displayId The logical display id.
* @param animate {@code true} to animate the transition, {@code false}
@@ -268,7 +232,7 @@ public class MagnificationProcessor {
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
- public boolean reset(int displayId, boolean animate) {
+ public boolean resetCurrentMagnification(int displayId, boolean animate) {
int mode = getControllingMode(displayId);
if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
return mController.getFullScreenMagnificationController().reset(displayId, animate);
@@ -279,6 +243,19 @@ public class MagnificationProcessor {
}
/**
+ * Resets the full-screen magnification on the given display.
+ *
+ * @param displayId The logical display id.
+ * @param animate {@code true} to animate the transition, {@code false}
+ * to transition immediately
+ * @return {@code true} if the magnification spec changed, {@code false} if
+ * the spec did not change
+ */
+ public boolean resetFullscreenMagnification(int displayId, boolean animate) {
+ return mController.getFullScreenMagnificationController().reset(displayId, animate);
+ }
+
+ /**
* {@link FullScreenMagnificationController#resetIfNeeded(int, boolean)}
*/
// TODO: support window magnification
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index b32d5434558b..9c996f452cf6 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -177,6 +177,7 @@ public class CompanionDeviceManagerService extends SystemService {
new BluetoothDeviceConnectedListener();
private BleStateBroadcastReceiver mBleStateBroadcastReceiver = new BleStateBroadcastReceiver();
private List<String> mCurrentlyConnectedDevices = new ArrayList<>();
+ Set<Integer> mPresentSelfManagedDevices = new HashSet<>();
private ArrayMap<String, Date> mDevicesLastNearby = new ArrayMap<>();
private UnbindDeviceListenersRunnable
mUnbindDeviceListenersRunnable = new UnbindDeviceListenersRunnable();
@@ -217,7 +218,7 @@ public class CompanionDeviceManagerService extends SystemService {
mPermissionControllerManager = requireNonNull(
context.getSystemService(PermissionControllerManager.class));
mUserManager = context.getSystemService(UserManager.class);
- mCompanionDevicePresenceController = new CompanionDevicePresenceController();
+ mCompanionDevicePresenceController = new CompanionDevicePresenceController(this);
mAssociationRequestsProcessor = new AssociationRequestsProcessor(this);
registerPackageMonitor();
@@ -555,6 +556,57 @@ public class CompanionDeviceManagerService extends SystemService {
//TODO: b/199427116
}
+ @Override
+ public void notifyDeviceAppeared(int associationId) {
+ final AssociationInfo association = getAssociationWithCallerChecks(associationId);
+ if (association == null) {
+ throw new IllegalArgumentException("Association with ID " + associationId + " "
+ + "does not exist "
+ + "or belongs to a different package "
+ + "or belongs to a different user");
+ }
+
+ if (!association.isSelfManaged()) {
+ throw new IllegalArgumentException("Association with ID " + associationId
+ + " is not self-managed. notifyDeviceAppeared(int) can only be called for"
+ + " self-managed associations.");
+ }
+
+ if (!mPresentSelfManagedDevices.add(associationId)) {
+ Slog.w(LOG_TAG, "Association with ID " + associationId + " is already present");
+ return;
+ }
+
+ mCompanionDevicePresenceController.onDeviceNotifyAppeared(
+ association, getContext(), mMainHandler);
+ }
+
+ @Override
+ public void notifyDeviceDisappeared(int associationId) {
+ final AssociationInfo association = getAssociationWithCallerChecks(associationId);
+ if (association == null) {
+ throw new IllegalArgumentException("Association with ID " + associationId + " "
+ + "does not exist "
+ + "or belongs to a different package "
+ + "or belongs to a different user");
+ }
+
+ if (!association.isSelfManaged()) {
+ throw new IllegalArgumentException("Association with ID " + associationId
+ + " is not self-managed. notifyDeviceAppeared(int) can only be called for"
+ + " self-managed associations.");
+ }
+
+ if (!mPresentSelfManagedDevices.contains(associationId)) {
+ Slog.w(LOG_TAG, "Association with ID " + associationId + " is not connected");
+ return;
+ }
+
+ mPresentSelfManagedDevices.remove(associationId);
+ mCompanionDevicePresenceController.onDeviceNotifyDisappearedAndUnbind(
+ association, getContext(), mMainHandler);
+ }
+
private void registerDevicePresenceListenerActive(String packageName, String deviceAddress,
boolean active) throws RemoteException {
getContext().enforceCallingOrSelfPermission(
@@ -645,11 +697,18 @@ public class CompanionDeviceManagerService extends SystemService {
}
}
+
fout.append("Currently Connected Devices:").append('\n');
for (int i = 0, size = mCurrentlyConnectedDevices.size(); i < size; i++) {
fout.append(" ").append(mCurrentlyConnectedDevices.get(i)).append('\n');
}
+ fout.append("Currently SelfManaged Connected Devices associationId:").append('\n');
+ for (Integer associationId : mPresentSelfManagedDevices) {
+ fout.append(" ").append("AssociationId: ").append(
+ String.valueOf(associationId)).append('\n');
+ }
+
fout.append("Devices Last Nearby:").append('\n');
for (int i = 0, size = mDevicesLastNearby.size(); i < size; i++) {
String device = mDevicesLastNearby.keyAt(i);
@@ -774,7 +833,9 @@ public class CompanionDeviceManagerService extends SystemService {
}
void onAssociationPreRemove(AssociationInfo association) {
- if (association.isNotifyOnDeviceNearby()) {
+ if (association.isNotifyOnDeviceNearby()
+ || (association.isSelfManaged()
+ && mPresentSelfManagedDevices.contains(association.getId()))) {
mCompanionDevicePresenceController.unbindDevicePresenceListener(
association.getPackageName(), association.getUserId());
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java
index 3e008467c9eb..444768491660 100644
--- a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java
+++ b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java
@@ -49,8 +49,10 @@ public class CompanionDevicePresenceController {
private static final String LOG_TAG = "CompanionDevicePresenceController";
PerUser<ArrayMap<String, List<BoundService>>> mBoundServices;
private static final String META_DATA_KEY_PRIMARY = "primary";
+ private final CompanionDeviceManagerService mService;
- public CompanionDevicePresenceController() {
+ public CompanionDevicePresenceController(CompanionDeviceManagerService service) {
+ mService = service;
mBoundServices = new PerUser<ArrayMap<String, List<BoundService>>>() {
@NonNull
@Override
@@ -61,24 +63,45 @@ public class CompanionDevicePresenceController {
}
void onDeviceNotifyAppeared(AssociationInfo association, Context context, Handler handler) {
- ServiceConnector<ICompanionDeviceService> primaryConnector =
- getPrimaryServiceConnector(association, context, handler);
- if (primaryConnector != null) {
- Slog.i(LOG_TAG,
- "Sending onDeviceAppeared to " + association.getPackageName() + ")");
- primaryConnector.run(
- s -> s.onDeviceAppeared(association.getDeviceMacAddressAsString()));
+ for (BoundService boundService : getDeviceListenerServiceConnector(
+ association, context, handler)) {
+ if (boundService.mIsPrimary) {
+ Slog.i(LOG_TAG,
+ "Sending onDeviceAppeared to " + association.getPackageName() + ")");
+ boundService.mServiceConnector.run(
+ service -> service.onDeviceAppeared(association));
+ } else {
+ Slog.i(LOG_TAG, "Connecting to " + boundService.mComponentName);
+ boundService.mServiceConnector.connect();
+ }
}
}
void onDeviceNotifyDisappeared(AssociationInfo association, Context context, Handler handler) {
- ServiceConnector<ICompanionDeviceService> primaryConnector =
- getPrimaryServiceConnector(association, context, handler);
- if (primaryConnector != null) {
- Slog.i(LOG_TAG,
- "Sending onDeviceDisappeared to " + association.getPackageName() + ")");
- primaryConnector.run(
- s -> s.onDeviceDisappeared(association.getDeviceMacAddressAsString()));
+ for (BoundService boundService : getDeviceListenerServiceConnector(
+ association, context, handler)) {
+ if (boundService.mIsPrimary) {
+ Slog.i(LOG_TAG,
+ "Sending onDeviceDisappeared to " + association.getPackageName() + ")");
+ boundService.mServiceConnector.run(service ->
+ service.onDeviceDisappeared(association));
+ }
+ }
+ }
+
+ void onDeviceNotifyDisappearedAndUnbind(AssociationInfo association,
+ Context context, Handler handler) {
+ for (BoundService boundService : getDeviceListenerServiceConnector(
+ association, context, handler)) {
+ if (boundService.mIsPrimary) {
+ Slog.i(LOG_TAG,
+ "Sending onDeviceDisappeared to " + association.getPackageName() + ")");
+ boundService.mServiceConnector.post(
+ service -> {
+ service.onDeviceDisappeared(association);
+ }).thenRun(() -> unbindDevicePresenceListener(
+ association.getPackageName(), association.getUserId()));
+ }
}
}
@@ -93,17 +116,6 @@ public class CompanionDevicePresenceController {
}
}
- private ServiceConnector<ICompanionDeviceService> getPrimaryServiceConnector(
- AssociationInfo association, Context context, Handler handler) {
- for (BoundService boundService: getDeviceListenerServiceConnector(association, context,
- handler)) {
- if (boundService.mIsPrimary) {
- return boundService.mServiceConnector;
- }
- }
- return null;
- }
-
private List<BoundService> getDeviceListenerServiceConnector(AssociationInfo a, Context context,
Handler handler) {
return mBoundServices.forUser(a.getUserId()).computeIfAbsent(
@@ -140,18 +152,22 @@ public class CompanionDevicePresenceController {
protected long getAutoDisconnectTimeoutMs() {
// Service binding is managed manually based on corresponding device
// being nearby
- return Long.MAX_VALUE;
+ return -1;
}
@Override
public void binderDied() {
super.binderDied();
-
- // Re-connect to the service if process gets killed
- handler.postDelayed(
- this::connect,
- CompanionDeviceManagerService
- .DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS);
+ if (a.isSelfManaged()) {
+ mBoundServices.forUser(a.getUserId()).remove(a.getPackageName());
+ mService.mPresentSelfManagedDevices.remove(a.getId());
+ } else {
+ // Re-connect to the service if process gets killed
+ handler.postDelayed(
+ this::connect,
+ CompanionDeviceManagerService
+ .DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS);
+ }
}
};
@@ -191,6 +207,13 @@ public class CompanionDevicePresenceController {
}
}
+ if (packageResolveInfos.size() > 1 && primaryCount == 0) {
+ Slog.e(LOG_TAG, "Must have exactly one primary CompanionDeviceService "
+ + "to be bound when declare more than one CompanionDeviceService but "
+ + association.getPackageName() + " has " + primaryCount);
+ return false;
+ }
+
if (packageResolveInfos.size() == 1 && primaryCount != 0) {
Slog.w(LOG_TAG, "Do not need the primary metadata if there's only one"
+ " CompanionDeviceService " + "but " + association.getPackageName()
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
new file mode 100644
index 000000000000..067edcc0b08d
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual;
+
+import android.annotation.NonNull;
+import android.graphics.Point;
+import android.hardware.input.VirtualKeyEvent;
+import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseRelativeEvent;
+import android.hardware.input.VirtualMouseScrollEvent;
+import android.hardware.input.VirtualTouchEvent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+/** Controls virtual input devices, including device lifecycle and event dispatch. */
+class InputController {
+
+ private final Object mLock;
+
+ /* Token -> file descriptor associations. */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ final Map<IBinder, Integer> mInputDeviceFds = new ArrayMap<>();
+
+ private final NativeWrapper mNativeWrapper;
+
+ InputController(@NonNull Object lock) {
+ this(lock, new NativeWrapper());
+ }
+
+ @VisibleForTesting
+ InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper) {
+ mLock = lock;
+ mNativeWrapper = nativeWrapper;
+ }
+
+ void close() {
+ synchronized (mLock) {
+ for (int fd : mInputDeviceFds.values()) {
+ mNativeWrapper.closeUinput(fd);
+ }
+ mInputDeviceFds.clear();
+ }
+ }
+
+ void createKeyboard(@NonNull String deviceName,
+ int vendorId,
+ int productId,
+ @NonNull IBinder deviceToken) {
+ final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId);
+ if (fd < 0) {
+ throw new RuntimeException(
+ "A native error occurred when creating keyboard: " + -fd);
+ }
+ synchronized (mLock) {
+ mInputDeviceFds.put(deviceToken, fd);
+ }
+ try {
+ deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Could not create virtual keyboard", e);
+ }
+ }
+
+ void createMouse(@NonNull String deviceName,
+ int vendorId,
+ int productId,
+ @NonNull IBinder deviceToken) {
+ final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId);
+ if (fd < 0) {
+ throw new RuntimeException(
+ "A native error occurred when creating mouse: " + -fd);
+ }
+ synchronized (mLock) {
+ mInputDeviceFds.put(deviceToken, fd);
+ }
+ try {
+ deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Could not create virtual mouse", e);
+ }
+ }
+
+ void createTouchscreen(@NonNull String deviceName,
+ int vendorId,
+ int productId,
+ @NonNull IBinder deviceToken,
+ @NonNull Point screenSize) {
+ final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
+ screenSize.y, screenSize.x);
+ if (fd < 0) {
+ throw new RuntimeException(
+ "A native error occurred when creating touchscreen: " + -fd);
+ }
+ synchronized (mLock) {
+ mInputDeviceFds.put(deviceToken, fd);
+ }
+ try {
+ deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Could not create virtual touchscreen", e);
+ }
+ }
+
+ void unregisterInputDevice(@NonNull IBinder token) {
+ synchronized (mLock) {
+ final Integer fd = mInputDeviceFds.remove(token);
+ if (fd == null) {
+ throw new IllegalArgumentException(
+ "Could not unregister input device for given token");
+ }
+ mNativeWrapper.closeUinput(fd);
+ }
+ }
+
+ boolean sendKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) {
+ synchronized (mLock) {
+ final Integer fd = mInputDeviceFds.get(token);
+ if (fd == null) {
+ throw new IllegalArgumentException(
+ "Could not send key event to input device for given token");
+ }
+ return mNativeWrapper.writeKeyEvent(fd, event.getKeyCode(), event.getAction());
+ }
+ }
+
+ boolean sendButtonEvent(@NonNull IBinder token, @NonNull VirtualMouseButtonEvent event) {
+ synchronized (mLock) {
+ final Integer fd = mInputDeviceFds.get(token);
+ if (fd == null) {
+ throw new IllegalArgumentException(
+ "Could not send button event to input device for given token");
+ }
+ return mNativeWrapper.writeButtonEvent(fd, event.getButtonCode(), event.getAction());
+ }
+ }
+
+ boolean sendTouchEvent(@NonNull IBinder token, @NonNull VirtualTouchEvent event) {
+ synchronized (mLock) {
+ final Integer fd = mInputDeviceFds.get(token);
+ if (fd == null) {
+ throw new IllegalArgumentException(
+ "Could not send touch event to input device for given token");
+ }
+ return mNativeWrapper.writeTouchEvent(fd, event.getPointerId(), event.getToolType(),
+ event.getAction(), event.getX(), event.getY(), event.getPressure(),
+ event.getMajorAxisSize());
+ }
+ }
+
+ boolean sendRelativeEvent(@NonNull IBinder token, @NonNull VirtualMouseRelativeEvent event) {
+ synchronized (mLock) {
+ final Integer fd = mInputDeviceFds.get(token);
+ if (fd == null) {
+ throw new IllegalArgumentException(
+ "Could not send relative event to input device for given token");
+ }
+ return mNativeWrapper.writeRelativeEvent(fd, event.getRelativeX(),
+ event.getRelativeY());
+ }
+ }
+
+ boolean sendScrollEvent(@NonNull IBinder token, @NonNull VirtualMouseScrollEvent event) {
+ synchronized (mLock) {
+ final Integer fd = mInputDeviceFds.get(token);
+ if (fd == null) {
+ throw new IllegalArgumentException(
+ "Could not send scroll event to input device for given token");
+ }
+ return mNativeWrapper.writeScrollEvent(fd, event.getXAxisMovement(),
+ event.getYAxisMovement());
+ }
+ }
+
+ public void dump(@NonNull PrintWriter fout) {
+ fout.println(" InputController: ");
+ synchronized (mLock) {
+ fout.println(" Active file descriptors: ");
+ for (int inputDeviceFd : mInputDeviceFds.values()) {
+ fout.println(inputDeviceFd);
+ }
+ }
+ }
+
+ private static native int nativeOpenUinputKeyboard(String deviceName, int vendorId,
+ int productId);
+ private static native int nativeOpenUinputMouse(String deviceName, int vendorId,
+ int productId);
+ private static native int nativeOpenUinputTouchscreen(String deviceName, int vendorId,
+ int productId, int height, int width);
+ private static native boolean nativeCloseUinput(int fd);
+ private static native boolean nativeWriteKeyEvent(int fd, int androidKeyCode, int action);
+ private static native boolean nativeWriteButtonEvent(int fd, int buttonCode, int action);
+ private static native boolean nativeWriteTouchEvent(int fd, int pointerId, int toolType,
+ int action, float locationX, float locationY, float pressure, float majorAxisSize);
+ private static native boolean nativeWriteRelativeEvent(int fd, float relativeX,
+ float relativeY);
+ private static native boolean nativeWriteScrollEvent(int fd, float xAxisMovement,
+ float yAxisMovement);
+
+ /** Wrapper around the static native methods for tests. */
+ @VisibleForTesting
+ protected static class NativeWrapper {
+ public int openUinputKeyboard(String deviceName, int vendorId, int productId) {
+ return nativeOpenUinputKeyboard(deviceName, vendorId,
+ productId);
+ }
+
+ public int openUinputMouse(String deviceName, int vendorId, int productId) {
+ return nativeOpenUinputMouse(deviceName, vendorId,
+ productId);
+ }
+
+ public int openUinputTouchscreen(String deviceName, int vendorId, int productId, int height,
+ int width) {
+ return nativeOpenUinputTouchscreen(deviceName, vendorId,
+ productId, height, width);
+ }
+
+ public boolean closeUinput(int fd) {
+ return nativeCloseUinput(fd);
+ }
+
+ public boolean writeKeyEvent(int fd, int androidKeyCode, int action) {
+ return nativeWriteKeyEvent(fd, androidKeyCode, action);
+ }
+
+ public boolean writeButtonEvent(int fd, int buttonCode, int action) {
+ return nativeWriteButtonEvent(fd, buttonCode, action);
+ }
+
+ public boolean writeTouchEvent(int fd, int pointerId, int toolType, int action,
+ float locationX, float locationY, float pressure, float majorAxisSize) {
+ return nativeWriteTouchEvent(fd, pointerId, toolType,
+ action, locationX, locationY,
+ pressure, majorAxisSize);
+ }
+
+ public boolean writeRelativeEvent(int fd, float relativeX, float relativeY) {
+ return nativeWriteRelativeEvent(fd, relativeX, relativeY);
+ }
+
+ public boolean writeScrollEvent(int fd, float xAxisMovement, float yAxisMovement) {
+ return nativeWriteScrollEvent(fd, xAxisMovement,
+ yAxisMovement);
+ }
+ }
+
+ private final class BinderDeathRecipient implements IBinder.DeathRecipient {
+
+ private final IBinder mDeviceToken;
+
+ BinderDeathRecipient(IBinder deviceToken) {
+ mDeviceToken = deviceToken;
+ }
+
+ @Override
+ public void binderDied() {
+ unregisterInputDevice(mDeviceToken);
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
new file mode 100644
index 000000000000..022da4361be4
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual;
+
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
+import android.annotation.NonNull;
+import android.companion.AssociationInfo;
+import android.companion.virtual.IVirtualDevice;
+import android.content.Context;
+import android.graphics.Point;
+import android.hardware.input.VirtualKeyEvent;
+import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseRelativeEvent;
+import android.hardware.input.VirtualMouseScrollEvent;
+import android.hardware.input.VirtualTouchEvent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.window.DisplayWindowPolicyController;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+
+final class VirtualDeviceImpl extends IVirtualDevice.Stub
+ implements IBinder.DeathRecipient {
+
+ private final Object mVirtualDeviceLock = new Object();
+
+ private final Context mContext;
+ private final AssociationInfo mAssociationInfo;
+ private final int mOwnerUid;
+ private final GenericWindowPolicyController mGenericWindowPolicyController;
+ private final InputController mInputController;
+ @VisibleForTesting
+ final List<Integer> mVirtualDisplayIds = new ArrayList<>();
+ private final OnDeviceCloseListener mListener;
+
+ VirtualDeviceImpl(Context context, AssociationInfo associationInfo,
+ IBinder token, int ownerUid, OnDeviceCloseListener listener) {
+ this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener);
+ }
+
+ @VisibleForTesting
+ VirtualDeviceImpl(Context context, AssociationInfo associationInfo, IBinder token,
+ int ownerUid, InputController inputController, OnDeviceCloseListener listener) {
+ mContext = context;
+ mAssociationInfo = associationInfo;
+ mGenericWindowPolicyController = new GenericWindowPolicyController(FLAG_SECURE,
+ SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ mOwnerUid = ownerUid;
+ if (inputController == null) {
+ mInputController = new InputController(mVirtualDeviceLock);
+ } else {
+ mInputController = inputController;
+ }
+ mListener = listener;
+ try {
+ token.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public int getAssociationId() {
+ return mAssociationInfo.getId();
+ }
+
+ @Override // Binder call
+ public void close() {
+ mListener.onClose(mAssociationInfo.getId());
+ mInputController.close();
+ }
+
+ @Override
+ public void binderDied() {
+ close();
+ }
+
+ @Override // Binder call
+ public void createVirtualKeyboard(
+ int displayId,
+ @NonNull String deviceName,
+ int vendorId,
+ int productId,
+ @NonNull IBinder deviceToken) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ "Permission required to create a virtual keyboard");
+ synchronized (mVirtualDeviceLock) {
+ if (!mVirtualDisplayIds.contains(displayId)) {
+ throw new SecurityException(
+ "Cannot create a virtual keyboard for a display not associated with "
+ + "this virtual device");
+ }
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
+ public void createVirtualMouse(
+ int displayId,
+ @NonNull String deviceName,
+ int vendorId,
+ int productId,
+ @NonNull IBinder deviceToken) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ "Permission required to create a virtual mouse");
+ synchronized (mVirtualDeviceLock) {
+ if (!mVirtualDisplayIds.contains(displayId)) {
+ throw new SecurityException(
+ "Cannot create a virtual mouse for a display not associated with this "
+ + "virtual device");
+ }
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mInputController.createMouse(deviceName, vendorId, productId, deviceToken);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
+ public void createVirtualTouchscreen(
+ int displayId,
+ @NonNull String deviceName,
+ int vendorId,
+ int productId,
+ @NonNull IBinder deviceToken,
+ @NonNull Point screenSize) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ "Permission required to create a virtual touchscreen");
+ synchronized (mVirtualDeviceLock) {
+ if (!mVirtualDisplayIds.contains(displayId)) {
+ throw new SecurityException(
+ "Cannot create a virtual touchscreen for a display not associated with "
+ + "this virtual device");
+ }
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mInputController.createTouchscreen(deviceName, vendorId, productId,
+ deviceToken, screenSize);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
+ public void unregisterInputDevice(IBinder token) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ "Permission required to unregister this input device");
+
+ final long binderToken = Binder.clearCallingIdentity();
+ try {
+ mInputController.unregisterInputDevice(token);
+ } finally {
+ Binder.restoreCallingIdentity(binderToken);
+ }
+ }
+
+ @Override // Binder call
+ public boolean sendKeyEvent(IBinder token, VirtualKeyEvent event) {
+ final long binderToken = Binder.clearCallingIdentity();
+ try {
+ return mInputController.sendKeyEvent(token, event);
+ } finally {
+ Binder.restoreCallingIdentity(binderToken);
+ }
+ }
+
+ @Override // Binder call
+ public boolean sendButtonEvent(IBinder token, VirtualMouseButtonEvent event) {
+ final long binderToken = Binder.clearCallingIdentity();
+ try {
+ return mInputController.sendButtonEvent(token, event);
+ } finally {
+ Binder.restoreCallingIdentity(binderToken);
+ }
+ }
+
+ @Override // Binder call
+ public boolean sendTouchEvent(IBinder token, VirtualTouchEvent event) {
+ final long binderToken = Binder.clearCallingIdentity();
+ try {
+ return mInputController.sendTouchEvent(token, event);
+ } finally {
+ Binder.restoreCallingIdentity(binderToken);
+ }
+ }
+
+ @Override // Binder call
+ public boolean sendRelativeEvent(IBinder token, VirtualMouseRelativeEvent event) {
+ final long binderToken = Binder.clearCallingIdentity();
+ try {
+ return mInputController.sendRelativeEvent(token, event);
+ } finally {
+ Binder.restoreCallingIdentity(binderToken);
+ }
+ }
+
+ @Override // Binder call
+ public boolean sendScrollEvent(IBinder token, VirtualMouseScrollEvent event) {
+ final long binderToken = Binder.clearCallingIdentity();
+ try {
+ return mInputController.sendScrollEvent(token, event);
+ } finally {
+ Binder.restoreCallingIdentity(binderToken);
+ }
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ fout.println(" VirtualDevice: ");
+ fout.println(" mVirtualDisplayIds: ");
+ synchronized (mVirtualDeviceLock) {
+ for (int id : mVirtualDisplayIds) {
+ fout.println(" " + id);
+ }
+ }
+ mInputController.dump(fout);
+ }
+
+ DisplayWindowPolicyController onVirtualDisplayCreatedLocked(int displayId) {
+ if (mVirtualDisplayIds.contains(displayId)) {
+ throw new IllegalStateException(
+ "Virtual device already have a virtual display with ID " + displayId);
+ }
+ mVirtualDisplayIds.add(displayId);
+ return mGenericWindowPolicyController;
+ }
+
+ void onVirtualDisplayRemovedLocked(int displayId) {
+ if (!mVirtualDisplayIds.contains(displayId)) {
+ throw new IllegalStateException(
+ "Virtual device doesn't have a virtual display with ID " + displayId);
+ }
+ mVirtualDisplayIds.remove(displayId);
+ }
+
+ int getOwnerUid() {
+ return mOwnerUid;
+ }
+
+ interface OnDeviceCloseListener {
+ void onClose(int associationId);
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 27426089f409..46e75f75ea9f 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -16,9 +16,6 @@
package com.android.server.companion.virtual;
-import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
-import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -42,7 +39,6 @@ import com.android.server.SystemService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@@ -52,6 +48,7 @@ public class VirtualDeviceManagerService extends SystemService {
private static final boolean DEBUG = false;
private static final String LOG_TAG = "VirtualDeviceManagerService";
+
private final Object mVirtualDeviceManagerLock = new Object();
private final VirtualDeviceManagerImpl mImpl;
@@ -130,64 +127,9 @@ public class VirtualDeviceManagerService extends SystemService {
}
}
- private class VirtualDeviceImpl extends IVirtualDevice.Stub implements IBinder.DeathRecipient {
-
- private final AssociationInfo mAssociationInfo;
- private final int mOwnerUid;
- private final GenericWindowPolicyController mGenericWindowPolicyController;
- private final ArrayList<Integer> mDisplayIds = new ArrayList<>();
-
- private VirtualDeviceImpl(int ownerUid, IBinder token, AssociationInfo associationInfo) {
- mOwnerUid = ownerUid;
- mAssociationInfo = associationInfo;
- mGenericWindowPolicyController = new GenericWindowPolicyController(FLAG_SECURE,
- SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
- try {
- token.linkToDeath(this, 0);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- mVirtualDevices.put(associationInfo.getId(), this);
- }
-
- @Override
- public int getAssociationId() {
- return mAssociationInfo.getId();
- }
-
- @Override
- public void close() {
- synchronized (mVirtualDeviceManagerLock) {
- mVirtualDevices.remove(mAssociationInfo.getId());
- }
- }
-
- @Override
- public void binderDied() {
- close();
- }
-
- DisplayWindowPolicyController onVirtualDisplayCreatedLocked(int displayId) {
- if (mDisplayIds.contains(displayId)) {
- throw new IllegalStateException(
- "Virtual device already have a virtual display with ID " + displayId);
- }
- mDisplayIds.add(displayId);
- return mGenericWindowPolicyController;
- }
-
- void onVirtualDisplayRemovedLocked(int displayId) {
- if (!mDisplayIds.contains(displayId)) {
- throw new IllegalStateException(
- "Virtual device doesn't have a virtual display with ID " + displayId);
- }
- mDisplayIds.remove(displayId);
- }
- }
-
class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub {
- @Override
+ @Override // Binder call
public IVirtualDevice createVirtualDevice(
IBinder token, String packageName, int associationId) {
getContext().enforceCallingOrSelfPermission(
@@ -209,7 +151,18 @@ public class VirtualDeviceManagerService extends SystemService {
"Virtual device for association ID " + associationId
+ " already exists");
}
- return new VirtualDeviceImpl(callingUid, token, associationInfo);
+ VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
+ associationInfo, token, callingUid,
+ new VirtualDeviceImpl.OnDeviceCloseListener() {
+ @Override
+ public void onClose(int associationId) {
+ synchronized (mVirtualDeviceManagerLock) {
+ mVirtualDevices.remove(associationId);
+ }
+ }
+ });
+ mVirtualDevices.put(associationInfo.getId(), virtualDevice);
+ return virtualDevice;
}
}
@@ -254,8 +207,7 @@ public class VirtualDeviceManagerService extends SystemService {
fout.println("Created virtual devices: ");
synchronized (mVirtualDeviceManagerLock) {
for (int i = 0; i < mVirtualDevices.size(); i++) {
- VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i);
- fout.printf("%d: %s\n", mVirtualDevices.keyAt(i), virtualDevice);
+ mVirtualDevices.valueAt(i).dump(fd, fout, args);
}
}
}
@@ -290,7 +242,7 @@ public class VirtualDeviceManagerService extends SystemService {
synchronized (mVirtualDeviceManagerLock) {
int size = mVirtualDevices.size();
for (int i = 0; i < size; i++) {
- if (mVirtualDevices.valueAt(i).mOwnerUid == uid) {
+ if (mVirtualDevices.valueAt(i).getOwnerUid() == uid) {
return true;
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a33aa600febd..a745e5afc6f0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -15659,6 +15659,11 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ @Override
+ public void enableBinderTracing() {
+ Binder.enableTracingForUid(Binder.getCallingUid());
+ }
+
@VisibleForTesting
public final class LocalService extends ActivityManagerInternal
implements ActivityManagerLocal {
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 20606f0e9f4d..8dce8e9e90ef 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -385,9 +385,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
public boolean isValid() {
- return (mGameMode == GameManager.GAME_MODE_PERFORMANCE
- || mGameMode == GameManager.GAME_MODE_BATTERY)
- && (!mAllowDownscale || getCompatChangeId() != 0);
+ return mGameMode == GameManager.GAME_MODE_PERFORMANCE
+ || mGameMode == GameManager.GAME_MODE_BATTERY;
}
/**
@@ -839,7 +838,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
long scaleId = modeConfig.getCompatChangeId();
if (scaleId == 0) {
- Slog.w(TAG, "Invalid downscaling change id " + scaleId + " for "
+ Slog.i(TAG, "Invalid downscaling change id " + scaleId + " for "
+ packageName);
return;
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6238198e9488..f857064a065a 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -10142,6 +10142,27 @@ public class AudioService extends IAudioService.Stub
return AudioManager.SUCCESS;
}
+ /** @see AudioPolicy#getFocusStack() */
+ public List<AudioFocusInfo> getFocusStack() {
+ enforceModifyAudioRoutingPermission();
+ return mMediaFocusControl.getFocusStack();
+ }
+
+ /** @see AudioPolicy#sendFocusLoss */
+ public boolean sendFocusLoss(@NonNull AudioFocusInfo focusLoser,
+ @NonNull IAudioPolicyCallback apcb) {
+ Objects.requireNonNull(focusLoser);
+ Objects.requireNonNull(apcb);
+ enforceModifyAudioRoutingPermission();
+ if (!mAudioPolicies.containsKey(apcb.asBinder())) {
+ throw new IllegalStateException("Only registered AudioPolicy can change focus");
+ }
+ if (!mAudioPolicies.get(apcb.asBinder()).mHasFocusListener) {
+ throw new IllegalStateException("AudioPolicy must have focus listener to change focus");
+ }
+ return mMediaFocusControl.sendFocusLoss(focusLoser);
+ }
+
/** see AudioManager.hasRegisteredDynamicPolicy */
public boolean hasRegisteredDynamicPolicy() {
synchronized (mAudioPolicies) {
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 66f62355c7f0..69a4c23cf867 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -27,6 +27,7 @@ import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.IAudioFocusDispatcher;
import android.media.MediaMetrics;
+import android.media.audiopolicy.AudioPolicy;
import android.media.audiopolicy.IAudioPolicyCallback;
import android.os.Binder;
import android.os.Build;
@@ -221,6 +222,51 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
}
}
+ /**
+ * Return a copy of the focus stack for external consumption (composed of AudioFocusInfo
+ * instead of FocusRequester instances)
+ * @return a SystemApi-friendly version of the focus stack, in the same order (last entry
+ * is top of focus stack, i.e. latest focus owner)
+ * @see AudioPolicy#getFocusStack()
+ */
+ @NonNull List<AudioFocusInfo> getFocusStack() {
+ synchronized (mAudioFocusLock) {
+ final ArrayList<AudioFocusInfo> stack = new ArrayList<>(mFocusStack.size());
+ for (FocusRequester fr : mFocusStack) {
+ stack.add(fr.toAudioFocusInfo());
+ }
+ return stack;
+ }
+ }
+
+ /**
+ * Send AUDIOFOCUS_LOSS to a specific stack entry.
+ * Note this method is supporting an external API, and is restricted to LOSS in order to
+ * prevent allowing the stack to be in an invalid state (e.g. entry inside stack has focus)
+ * @param focusLoser the stack entry that is exiting the stack through a focus loss
+ * @return false if the focusLoser wasn't found in the stack, true otherwise
+ * @see AudioPolicy#sendFocusLoss(AudioFocusInfo)
+ */
+ boolean sendFocusLoss(@NonNull AudioFocusInfo focusLoser) {
+ synchronized (mAudioFocusLock) {
+ FocusRequester loserToRemove = null;
+ for (FocusRequester fr : mFocusStack) {
+ if (fr.getClientId().equals(focusLoser.getClientId())) {
+ fr.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null,
+ false /*forceDuck*/);
+ loserToRemove = fr;
+ break;
+ }
+ }
+ if (loserToRemove != null) {
+ mFocusStack.remove(loserToRemove);
+ loserToRemove.release();
+ return true;
+ }
+ }
+ return false;
+ }
+
@GuardedBy("mAudioFocusLock")
private void notifyTopOfAudioFocusStack() {
// notify the top of the stack it gained focus
diff --git a/services/core/java/com/android/server/communal/OWNERS b/services/core/java/com/android/server/communal/OWNERS
index e499b160beae..b02883da854a 100644
--- a/services/core/java/com/android/server/communal/OWNERS
+++ b/services/core/java/com/android/server/communal/OWNERS
@@ -1,3 +1,4 @@
brycelee@google.com
justinkoh@google.com
-lusilva@google.com \ No newline at end of file
+lusilva@google.com
+xilei@google.com \ No newline at end of file
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 05e1bdd11db6..3d91feef7043 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -39,6 +39,7 @@ import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArrayMap;
+import android.util.EventLog;
import android.util.Slog;
import android.view.IWindowManager;
import android.view.WindowManager;
@@ -49,6 +50,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.InputBindResult;
import com.android.internal.inputmethod.UnbindReason;
import com.android.internal.view.IInputMethod;
+import com.android.server.EventLogTags;
import com.android.server.wm.WindowManagerInternal;
/**
@@ -58,6 +60,9 @@ final class InputMethodBindingController {
static final boolean DEBUG = false;
private static final String TAG = InputMethodBindingController.class.getSimpleName();
+ /** Time in milliseconds that the IME service has to bind before it is reconnected. */
+ static final long TIME_TO_RECONNECT = 3 * 1000;
+
@NonNull private final InputMethodManagerService mService;
@NonNull private final Context mContext;
@NonNull private final ArrayMap<String, InputMethodInfo> mMethodMap;
@@ -239,7 +244,7 @@ final class InputMethodBindingController {
}
/**
- * Indicates whether {@link #getVisibleConnection} is currently in use.
+ * Indicates whether {@link #mVisibleConnection} is currently in use.
*/
boolean isVisibleBound() {
return mVisibleBound;
@@ -248,11 +253,6 @@ final class InputMethodBindingController {
/**
* Used to bring IME service up to visible adjustment while it is being shown.
*/
- @NonNull
- ServiceConnection getVisibleConnection() {
- return mVisibleConnection;
- }
-
private final ServiceConnection mVisibleConnection = new ServiceConnection() {
@Override public void onBindingDied(ComponentName name) {
synchronized (mMethodMap) {
@@ -334,7 +334,7 @@ final class InputMethodBindingController {
// We consider this to be a new bind attempt, since the system
// should now try to restart the service for us.
mLastBindTime = SystemClock.uptimeMillis();
- mService.clearClientSessionsLocked();
+ clearCurMethodAndSessionsLocked();
mService.clearInputShowRequestLocked();
mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME);
}
@@ -358,11 +358,12 @@ final class InputMethodBindingController {
}
mCurId = null;
- mService.clearClientSessionsLocked();
+ clearCurMethodAndSessionsLocked();
}
@GuardedBy("mMethodMap")
- void clearCurMethodLocked() {
+ private void clearCurMethodAndSessionsLocked() {
+ mService.clearClientSessionsLocked();
mCurMethod = null;
mCurMethodUid = Process.INVALID_UID;
}
@@ -382,7 +383,7 @@ final class InputMethodBindingController {
@GuardedBy("mMethodMap")
@NonNull
- InputBindResult bindCurrentMethodLocked(int displayIdToShowIme) {
+ InputBindResult bindCurrentMethodLocked() {
InputMethodInfo info = mMethodMap.get(mSelectedMethodId);
if (info == null) {
throw new IllegalArgumentException("Unknown id: " + mSelectedMethodId);
@@ -394,7 +395,7 @@ final class InputMethodBindingController {
mCurId = info.getId();
mLastBindTime = SystemClock.uptimeMillis();
- addFreshWindowTokenLocked(displayIdToShowIme);
+ addFreshWindowTokenLocked();
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
null, null, mCurId, mCurSeq, false);
@@ -419,7 +420,8 @@ final class InputMethodBindingController {
}
@GuardedBy("mMethodMap")
- private void addFreshWindowTokenLocked(int displayIdToShowIme) {
+ private void addFreshWindowTokenLocked() {
+ int displayIdToShowIme = mService.getDisplayIdToShowIme();
mCurToken = new Binder();
mService.setCurTokenDisplayId(displayIdToShowIme);
@@ -438,7 +440,7 @@ final class InputMethodBindingController {
}
@GuardedBy("mMethodMap")
- void unbindMainConnectionLocked() {
+ private void unbindMainConnectionLocked() {
mContext.unbindService(mMainConnection);
mHasConnection = false;
}
@@ -460,17 +462,61 @@ final class InputMethodBindingController {
}
@GuardedBy("mMethodMap")
- boolean bindCurrentInputMethodServiceVisibleConnectionLocked() {
+ private boolean bindCurrentInputMethodServiceVisibleConnectionLocked() {
mVisibleBound = bindCurrentInputMethodServiceLocked(mVisibleConnection,
IME_VISIBLE_BIND_FLAGS);
return mVisibleBound;
}
@GuardedBy("mMethodMap")
- boolean bindCurrentInputMethodServiceMainConnectionLocked() {
+ private boolean bindCurrentInputMethodServiceMainConnectionLocked() {
mHasConnection = bindCurrentInputMethodServiceLocked(mMainConnection,
mImeConnectionBindFlags);
return mHasConnection;
}
+ /**
+ * Bind the IME so that it can be shown.
+ *
+ * <p>
+ * Performs a rebind if no binding is achieved in {@link #TIME_TO_RECONNECT} milliseconds.
+ */
+ @GuardedBy("mMethodMap")
+ void setCurrentMethodVisibleLocked() {
+ if (mCurMethod != null) {
+ if (DEBUG) Slog.d(TAG, "setCurrentMethodVisibleLocked: mCurToken=" + mCurToken);
+ if (mHasConnection && !mVisibleBound) {
+ bindCurrentInputMethodServiceVisibleConnectionLocked();
+ }
+ return;
+ }
+
+ long bindingDuration = SystemClock.uptimeMillis() - mLastBindTime;
+ if (mHasConnection && bindingDuration >= TIME_TO_RECONNECT) {
+ // The client has asked to have the input method shown, but
+ // we have been sitting here too long with a connection to the
+ // service and no interface received, so let's disconnect/connect
+ // to try to prod things along.
+ EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, getSelectedMethodId(),
+ bindingDuration, 1);
+ Slog.w(TAG, "Force disconnect/connect to the IME in setCurrentMethodVisibleLocked()");
+ unbindMainConnectionLocked();
+ bindCurrentInputMethodServiceMainConnectionLocked();
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "Can't show input: connection = " + mHasConnection + ", time = "
+ + (TIME_TO_RECONNECT - bindingDuration));
+ }
+ }
+ }
+
+ /**
+ * Remove the binding needed for the IME to be shown.
+ */
+ @GuardedBy("mMethodMap")
+ void setCurrentMethodNotVisibleLocked() {
+ if (mVisibleBound) {
+ unbindVisibleConnectionLocked();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 3c6b0966dfc3..bff4f273e195 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -49,6 +49,8 @@ import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
+import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT;
+
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.Manifest;
@@ -111,12 +113,10 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
-import android.text.style.SuggestionSpan;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
import android.util.IndentingPrintWriter;
-import android.util.LruCache;
import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Printer;
@@ -250,10 +250,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000;
- static final long TIME_TO_RECONNECT = 3 * 1000;
-
- static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
-
private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
private static final String HANDLER_THREAD_NAME = "android.imms";
@@ -301,8 +297,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// lock for this class.
final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
final ArrayMap<String, InputMethodInfo> mMethodMap = new ArrayMap<>();
- private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans =
- new LruCache<>(SECURE_SUGGESTION_SPANS_MAX_SIZE);
final InputMethodSubtypeSwitchingController mSwitchingController;
/**
@@ -312,13 +306,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
private int mMethodMapUpdateCount = 0;
/**
- * Indicates whether {@link InputMethodBindingController#getVisibleConnection} is currently
- * in use.
+ * The display id for which the latest startInput was called.
*/
- private boolean isVisibleBound() {
- return mBindingController.isVisibleBound();
+ @GuardedBy("mMethodMap")
+ int getDisplayIdToShowIme() {
+ return mDisplayIdToShowIme;
}
+ @GuardedBy("mMethodMap")
+ private int mDisplayIdToShowIme = INVALID_DISPLAY;
+
// Ongoing notification
private NotificationManager mNotificationManager;
KeyguardManager mKeyguardManager;
@@ -2354,10 +2351,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
// Compute the final shown display ID with validated cs.selfReportedDisplayId for this
// session & other conditions.
- final int displayIdToShowIme = computeImeDisplayIdForTarget(cs.selfReportedDisplayId,
+ mDisplayIdToShowIme = computeImeDisplayIdForTarget(cs.selfReportedDisplayId,
mImeDisplayValidator);
- if (displayIdToShowIme == INVALID_DISPLAY) {
+ if (mDisplayIdToShowIme == INVALID_DISPLAY) {
mImeHiddenByDisplayPolicy = true;
hideCurrentInputLocked(mCurFocusedWindow, 0, null,
SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE);
@@ -2378,7 +2375,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// Check if the input method is changing.
// We expect the caller has already verified that the client is allowed to access this
// display ID.
- if (isSelectedMethodBound(displayIdToShowIme)) {
+ if (isSelectedMethodBound()) {
if (cs.curSession != null) {
// Fast case: if we are already connected to the input method,
// then just return it.
@@ -2394,13 +2391,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mBindingController.unbindCurrentMethodLocked();
- return mBindingController.bindCurrentMethodLocked(displayIdToShowIme);
+ return mBindingController.bindCurrentMethodLocked();
}
- private boolean isSelectedMethodBound(int displayIdToShowIme) {
+ private boolean isSelectedMethodBound() {
String curId = getCurId();
return curId != null && curId.equals(getSelectedMethodId())
- && displayIdToShowIme == mCurTokenDisplayId;
+ && mDisplayIdToShowIme == mCurTokenDisplayId;
}
@GuardedBy("mMethodMap")
@@ -2590,7 +2587,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
finishSessionLocked(mEnabledSession);
mEnabledSession = null;
- mBindingController.clearCurMethodLocked();
scheduleNotifyImeUidToAudioService(Process.INVALID_UID);
}
hideStatusBarIconLocked();
@@ -3047,42 +3043,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return false;
}
- boolean res = false;
- IInputMethod curMethod = getCurMethod();
- if (curMethod != null) {
- if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + getCurToken());
+ mBindingController.setCurrentMethodVisibleLocked();
+ if (getCurMethod() != null) {
// create a placeholder token for IMS so that IMS cannot inject windows into client app.
Binder showInputToken = new Binder();
mShowRequestWindowMap.put(showInputToken, windowToken);
+ IInputMethod curMethod = getCurMethod();
executeOrSendMessage(curMethod, mCaller.obtainMessageIIOOO(MSG_SHOW_SOFT_INPUT,
getImeShowFlagsLocked(), reason, curMethod, resultReceiver,
showInputToken));
mInputShown = true;
- if (hasConnection() && !isVisibleBound()) {
- mBindingController.bindCurrentInputMethodServiceVisibleConnectionLocked();
- }
- res = true;
- } else {
- long bindingDuration = SystemClock.uptimeMillis() - getLastBindTime();
- if (hasConnection() && bindingDuration >= TIME_TO_RECONNECT) {
- // The client has asked to have the input method shown, but
- // we have been sitting here too long with a connection to the
- // service and no interface received, so let's disconnect/connect
- // to try to prod things along.
- EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, getSelectedMethodId(),
- bindingDuration, 1);
- Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
- mBindingController.unbindMainConnectionLocked();
- mBindingController.bindCurrentInputMethodServiceMainConnectionLocked();
- } else {
- if (DEBUG) {
- Slog.d(TAG, "Can't show input: connection = " + hasConnection() + ", time = "
- + (TIME_TO_RECONNECT - bindingDuration));
- }
- }
+ return true;
}
- return res;
+ return false;
}
@Override
@@ -3166,9 +3140,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
} else {
res = false;
}
- if (hasConnection() && isVisibleBound()) {
- mBindingController.unbindVisibleConnectionLocked();
- }
+ mBindingController.setCurrentMethodNotVisibleLocked();
mInputShown = false;
mShowRequested = false;
mShowExplicitlyRequested = false;
@@ -3522,10 +3494,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return mWindowManagerInternal.shouldRestoreImeVisibility(windowToken);
}
- private boolean isImeVisible() {
- return (mImeWindowVis & InputMethodService.IME_VISIBLE) != 0;
- }
-
@GuardedBy("mMethodMap")
private boolean canShowInputMethodPickerLocked(IInputMethodClient client) {
// TODO(yukawa): multi-display support.
@@ -5114,7 +5082,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
+ " client=" + mCurFocusedWindowClient);
focusedWindowClient = mCurFocusedWindowClient;
p.println(" mCurId=" + getCurId() + " mHaveConnection=" + hasConnection()
- + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound=" + isVisibleBound());
+ + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
+ + mBindingController.isVisibleBound());
p.println(" mCurToken=" + getCurToken());
p.println(" mCurTokenDisplayId=" + mCurTokenDisplayId);
p.println(" mCurHostInputToken=" + mCurHostInputToken);
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 0ce24dda666e..ede8b32c9b11 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -177,7 +177,7 @@ public class LocationProviderManager extends
protected interface LocationTransport {
void deliverOnLocationChanged(LocationResult locationResult,
- @Nullable Runnable onCompleteCallback) throws Exception;
+ @Nullable IRemoteCallback onCompleteCallback) throws Exception;
void deliverOnFlushComplete(int requestCode) throws Exception;
}
@@ -197,9 +197,8 @@ public class LocationProviderManager extends
@Override
public void deliverOnLocationChanged(LocationResult locationResult,
- @Nullable Runnable onCompleteCallback) throws RemoteException {
- mListener.onLocationChanged(locationResult.asList(),
- SingleUseCallback.wrap(onCompleteCallback));
+ @Nullable IRemoteCallback onCompleteCallback) throws RemoteException {
+ mListener.onLocationChanged(locationResult.asList(), onCompleteCallback);
}
@Override
@@ -227,7 +226,7 @@ public class LocationProviderManager extends
@Override
public void deliverOnLocationChanged(LocationResult locationResult,
- @Nullable Runnable onCompleteCallback)
+ @Nullable IRemoteCallback onCompleteCallback)
throws PendingIntent.CanceledException {
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setDontSendToRestrictedApps(true);
@@ -243,20 +242,34 @@ public class LocationProviderManager extends
intent.putExtra(KEY_LOCATIONS, locationResult.asList().toArray(new Location[0]));
}
+ PendingIntent.OnFinished onFinished = null;
+
// send() SHOULD only run the completion callback if it completes successfully. however,
- // b/199464864 (which could not be fixed in the S timeframe) means that it's possible
+ // b/201299281 (which could not be fixed in the S timeframe) means that it's possible
// for send() to throw an exception AND run the completion callback. if this happens, we
// would over-release the wakelock... we take matters into our own hands to ensure that
// the completion callback can only be run if send() completes successfully. this means
// the completion callback may be run inline - but as we've never specified what thread
// the callback is run on, this is fine.
- GatedCallback gatedCallback = new GatedCallback(onCompleteCallback);
+ GatedCallback gatedCallback;
+ if (onCompleteCallback != null) {
+ gatedCallback = new GatedCallback(() -> {
+ try {
+ onCompleteCallback.sendResult(null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ });
+ onFinished = (pI, i, rC, rD, rE) -> gatedCallback.run();
+ } else {
+ gatedCallback = new GatedCallback(null);
+ }
mPendingIntent.send(
mContext,
0,
intent,
- (pI, i, rC, rD, rE) -> gatedCallback.run(),
+ onFinished,
null,
null,
options.toBundle());
@@ -293,7 +306,7 @@ public class LocationProviderManager extends
@Override
public void deliverOnLocationChanged(@Nullable LocationResult locationResult,
- @Nullable Runnable onCompleteCallback)
+ @Nullable IRemoteCallback onCompleteCallback)
throws RemoteException {
// ILocationCallback doesn't currently support completion callbacks
Preconditions.checkState(onCompleteCallback == null);
@@ -714,6 +727,13 @@ public class LocationProviderManager extends
final PowerManager.WakeLock mWakeLock;
+ // b/206340085 - if we allocate a new wakelock releaser object for every delivery we
+ // increase the risk of resource starvation. if a client stops processing deliveries the
+ // system server binder allocation pool will be starved as we continue to queue up
+ // deliveries, each with a new allocation. in order to mitigate this, we use a single
+ // releaser object per registration rather than per delivery.
+ final ExternalWakeLockReleaser mWakeLockReleaser;
+
private volatile ProviderTransport mProviderTransport;
private int mNumLocationsDelivered = 0;
private long mExpirationRealtimeMs = Long.MAX_VALUE;
@@ -727,6 +747,7 @@ public class LocationProviderManager extends
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
mWakeLock.setReferenceCounted(true);
mWakeLock.setWorkSource(request.getWorkSource());
+ mWakeLockReleaser = new ExternalWakeLockReleaser(identity, mWakeLock);
}
@Override
@@ -943,7 +964,7 @@ public class LocationProviderManager extends
}
listener.deliverOnLocationChanged(deliverLocationResult,
- mUseWakeLock ? mWakeLock::release : null);
+ mUseWakeLock ? mWakeLockReleaser : null);
EVENT_LOG.logProviderDeliveredLocations(mName, locationResult.size(),
getIdentity());
}
@@ -2761,7 +2782,7 @@ public class LocationProviderManager extends
@GuardedBy("this")
private boolean mRun;
- GatedCallback(Runnable callback) {
+ GatedCallback(@Nullable Runnable callback) {
mCallback = callback;
}
@@ -2796,4 +2817,24 @@ public class LocationProviderManager extends
}
}
}
+
+ private static class ExternalWakeLockReleaser extends IRemoteCallback.Stub {
+
+ private final CallerIdentity mIdentity;
+ private final PowerManager.WakeLock mWakeLock;
+
+ ExternalWakeLockReleaser(CallerIdentity identity, PowerManager.WakeLock wakeLock) {
+ mIdentity = identity;
+ mWakeLock = Objects.requireNonNull(wakeLock);
+ }
+
+ @Override
+ public void sendResult(Bundle data) {
+ try {
+ mWakeLock.release();
+ } catch (RuntimeException e) {
+ Log.e(TAG, "wakelock over-released by " + mIdentity, e);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index 99cb6f03e6da..24008d0f64ca 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -167,6 +167,7 @@ public final class PermissionHelper {
public void setNotificationPermission(String packageName, @UserIdInt int userId, boolean grant,
boolean userSet) {
assertFlag();
+ final long callingId = Binder.clearCallingIdentity();
try {
if (grant) {
mPermManager.grantRuntimePermission(packageName, NOTIFICATION_PERMISSION, userId);
@@ -180,13 +181,20 @@ public final class PermissionHelper {
}
} catch (RemoteException e) {
Slog.e(TAG, "Could not reach system server", e);
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
}
}
public void setNotificationPermission(PackagePermission pkgPerm) {
assertFlag();
- setNotificationPermission(
- pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted, pkgPerm.userSet);
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ setNotificationPermission(
+ pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted, pkgPerm.userSet);
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
}
public boolean isPermissionFixed(String packageName, @UserIdInt int userId) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 0564e858c84c..fdcf1fcfd260 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -18,6 +18,7 @@ package com.android.server.pm;
import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
+import android.Manifest;
import android.accounts.IAccountManager;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
@@ -170,6 +171,20 @@ class PackageManagerShellCommand extends ShellCommand {
@Override
public int onCommand(String cmd) {
+ switch (Binder.getCallingUid()) {
+ case Process.ROOT_UID:
+ case Process.SHELL_UID:
+ break;
+ default:
+ // This is called from a test and is allowed as non-shell with the right permission
+ if ("install-incremental".equals(cmd)) {
+ mContext.enforceCallingPermission(Manifest.permission.USE_SYSTEM_DATA_LOADERS,
+ "Caller missing USE_SYSTEM_DATA_LOADERS permission to use " + cmd);
+ } else {
+ throw new IllegalArgumentException("Caller must be root or shell");
+ }
+ }
+
if (cmd == null) {
return handleDefaultCommands(cmd);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index f6895195781c..167ad3ba7d0e 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -839,7 +839,7 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public @NonNull List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying,
boolean excludePreCreated) {
- checkManageOrCreateUsersPermission("query users");
+ checkCreateUsersPermission("query users");
return getUsersInternal(excludePartial, excludeDying, excludePreCreated);
}
@@ -868,7 +868,7 @@ public class UserManagerService extends IUserManager.Stub {
checkQueryOrCreateUsersPermission("getting profiles related to user " + userId);
returnFullInfo = true;
} else {
- returnFullInfo = hasManageOrCreateUsersPermission();
+ returnFullInfo = hasCreateUsersPermission();
}
final long ident = Binder.clearCallingIdentity();
try {
@@ -1708,7 +1708,7 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public boolean isRestricted(@UserIdInt int userId) {
if (userId != UserHandle.getCallingUserId()) {
- checkManageOrCreateUsersPermission("query isRestricted for user " + userId);
+ checkCreateUsersPermission("query isRestricted for user " + userId);
}
synchronized (mUsersLock) {
final UserInfo userInfo = getUserInfoLU(userId);
@@ -2227,7 +2227,7 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public boolean hasBaseUserRestriction(String restrictionKey, @UserIdInt int userId) {
- checkManageOrCreateUsersPermission("hasBaseUserRestriction");
+ checkCreateUsersPermission("hasBaseUserRestriction");
if (!UserRestrictionsUtils.isValidRestriction(restrictionKey)) {
return false;
}
@@ -2444,7 +2444,7 @@ public class UserManagerService extends IUserManager.Stub {
*/
@Override
public boolean canAddMoreUsersOfType(String userType) {
- checkManageOrCreateUsersPermission("check if more users can be added.");
+ checkCreateUsersPermission("check if more users can be added.");
final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
return userTypeDetails != null && canAddMoreUsersOfType(userTypeDetails);
}
@@ -2452,7 +2452,7 @@ public class UserManagerService extends IUserManager.Stub {
/** Returns whether the creation of users of the given user type is enabled on this device. */
@Override
public boolean isUserTypeEnabled(String userType) {
- checkManageOrCreateUsersPermission("check if user type is enabled.");
+ checkCreateUsersPermission("check if user type is enabled.");
final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
return userTypeDetails != null && userTypeDetails.isEnabled();
}
@@ -2577,10 +2577,10 @@ public class UserManagerService extends IUserManager.Stub {
*
* @param message used as message if SecurityException is thrown
* @throws SecurityException if the caller is not system or root
- * @see #hasManageOrCreateUsersPermission()
+ * @see #hasCreateUsersPermission()
*/
- private static final void checkManageOrCreateUsersPermission(String message) {
- if (!hasManageOrCreateUsersPermission()) {
+ private static final void checkCreateUsersPermission(String message) {
+ if (!hasCreateUsersPermission()) {
throw new SecurityException(
"You either need MANAGE_USERS or CREATE_USERS permission to: " + message);
}
@@ -2621,14 +2621,14 @@ public class UserManagerService extends IUserManager.Stub {
}
/**
- * Similar to {@link #checkManageOrCreateUsersPermission(String)} but when the caller is tries
+ * Similar to {@link #checkCreateUsersPermission(String)} but when the caller is tries
* to create user/profiles other than what is allowed for
* {@link android.Manifest.permission#CREATE_USERS CREATE_USERS} permission, then it will only
* allow callers with {@link android.Manifest.permission#MANAGE_USERS MANAGE_USERS} permission.
*/
- private static final void checkManageOrCreateUsersPermission(int creationFlags) {
+ private static final void checkCreateUsersPermission(int creationFlags) {
if ((creationFlags & ~ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION) == 0) {
- if (!hasManageOrCreateUsersPermission()) {
+ if (!hasCreateUsersPermission()) {
throw new SecurityException("You either need MANAGE_USERS or CREATE_USERS "
+ "permission to create an user with flags: " + creationFlags);
}
@@ -2672,7 +2672,7 @@ public class UserManagerService extends IUserManager.Stub {
* {@link android.Manifest.permission#MANAGE_USERS MANAGE_USERS} or
* {@link android.Manifest.permission#CREATE_USERS CREATE_USERS}.
*/
- private static final boolean hasManageOrCreateUsersPermission() {
+ private static final boolean hasCreateUsersPermission() {
return hasManageUsersOrPermission(android.Manifest.permission.CREATE_USERS);
}
@@ -2692,7 +2692,7 @@ public class UserManagerService extends IUserManager.Stub {
* {@link android.Manifest.permission#QUERY_USERS QUERY_USERS}.
*/
private static final boolean hasQueryOrCreateUsersPermission() {
- return hasManageOrCreateUsersPermission()
+ return hasCreateUsersPermission()
|| hasPermissionGranted(Manifest.permission.QUERY_USERS, Binder.getCallingUid());
}
@@ -3572,7 +3572,7 @@ public class UserManagerService extends IUserManager.Stub {
public UserInfo createProfileForUserWithThrow(@Nullable String name, @NonNull String userType,
@UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages)
throws ServiceSpecificException {
- checkManageOrCreateUsersPermission(flags);
+ checkCreateUsersPermission(flags);
try {
return createUserInternal(name, userType, flags, userId, disallowedPackages);
} catch (UserManager.CheckedUserOperationException e) {
@@ -3588,7 +3588,7 @@ public class UserManagerService extends IUserManager.Stub {
@NonNull String userType,
@UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages)
throws ServiceSpecificException {
- checkManageOrCreateUsersPermission(flags);
+ checkCreateUsersPermission(flags);
try {
return createUserInternalUnchecked(name, userType, flags, userId,
/* preCreate= */ false, disallowedPackages, /* token= */ null);
@@ -3601,7 +3601,7 @@ public class UserManagerService extends IUserManager.Stub {
public UserInfo createUserWithThrow(String name, @NonNull String userType,
@UserInfoFlag int flags)
throws ServiceSpecificException {
- checkManageOrCreateUsersPermission(flags);
+ checkCreateUsersPermission(flags);
try {
return createUserInternal(name, userType, flags, UserHandle.USER_NULL,
/* disallowedPackages= */ null);
@@ -3615,7 +3615,7 @@ public class UserManagerService extends IUserManager.Stub {
final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
final int flags = userTypeDetails != null ? userTypeDetails.getDefaultUserInfoFlags() : 0;
- checkManageOrCreateUsersPermission(flags);
+ checkCreateUsersPermission(flags);
Preconditions.checkArgument(isUserTypeEligibleForPreCreation(userTypeDetails),
"cannot pre-create user of type " + userType);
@@ -3635,7 +3635,7 @@ public class UserManagerService extends IUserManager.Stub {
String userName, String userType, @UserInfoFlag int flags,
Bitmap userIcon,
String accountName, String accountType, PersistableBundle accountOptions) {
- checkManageOrCreateUsersPermission(flags);
+ checkCreateUsersPermission(flags);
if (someUserHasAccountNoChecks(accountName, accountType)) {
throw new ServiceSpecificException(
@@ -4080,7 +4080,7 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public String[] getPreInstallableSystemPackages(@NonNull String userType) {
- checkManageOrCreateUsersPermission("getPreInstallableSystemPackages");
+ checkCreateUsersPermission("getPreInstallableSystemPackages");
final Set<String> installableSystemPackages =
mSystemPackageInstaller.getInstallablePackagesForUserType(userType);
if (installableSystemPackages == null) {
@@ -4205,7 +4205,7 @@ public class UserManagerService extends IUserManager.Stub {
*/
@Override
public UserInfo createRestrictedProfileWithThrow(@Nullable String name, int parentUserId) {
- checkManageOrCreateUsersPermission("setupRestrictedProfile");
+ checkCreateUsersPermission("setupRestrictedProfile");
final UserInfo user = createProfileForUserWithThrow(
name, UserManager.USER_TYPE_FULL_RESTRICTED, 0, parentUserId, null);
if (user == null) {
@@ -4302,7 +4302,7 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public boolean removeUser(@UserIdInt int userId) {
Slog.i(LOG_TAG, "removeUser u" + userId);
- checkManageOrCreateUsersPermission("Only the system can remove users");
+ checkCreateUsersPermission("Only the system can remove users");
final String restriction = getUserRemovalRestriction(userId);
if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) {
@@ -4314,7 +4314,7 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) {
- checkManageOrCreateUsersPermission("Only the system can remove users");
+ checkCreateUsersPermission("Only the system can remove users");
return removeUserUnchecked(userId);
}
@@ -4429,7 +4429,7 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public @UserManager.RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId,
boolean evenWhenDisallowed) {
- checkManageOrCreateUsersPermission("Only the system can remove users");
+ checkCreateUsersPermission("Only the system can remove users");
if (!evenWhenDisallowed) {
final String restriction = getUserRemovalRestriction(userId);
@@ -5180,7 +5180,7 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public boolean someUserHasAccount(String accountName, String accountType) {
- checkManageOrCreateUsersPermission("check seed account information");
+ checkCreateUsersPermission("check seed account information");
return someUserHasAccountNoChecks(accountName, accountType);
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 111087f0baf2..a3b0e3e7d02d 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -1197,6 +1197,7 @@ public class DomainVerificationService extends SystemService
@Nullable @UserIdInt Integer userId,
@NonNull Function<String, PackageStateInternal> pkgSettingFunction)
throws NameNotFoundException {
+ mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy);
synchronized (mLock) {
mDebug.printState(writer, packageName, userId, pkgSettingFunction, mAttachedPkgStates);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index eb6895264c60..44d36237b555 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -167,7 +167,6 @@ import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.ColorSpace;
-import android.graphics.GraphicBuffer;
import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
@@ -4092,8 +4091,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// Make IME snapshot as trusted overlay
InputMonitor.setTrustedOverlayInputInfo(imeSurface, t, getDisplayId(),
"IME-snapshot-surface");
- GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(buffer);
- t.setBuffer(imeSurface, graphicBuffer);
+ t.setBuffer(imeSurface, buffer);
t.setColorSpace(mSurfaceControl, ColorSpace.get(ColorSpace.Named.SRGB));
t.setRelativeLayer(imeSurface, activity.getSurfaceControl(), 1);
t.setPosition(imeSurface, mInputMethodWindow.getDisplayFrame().left,
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 94a175caba22..8a2d11636fe3 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -251,15 +251,47 @@ public class LockTaskController {
*/
boolean activityBlockedFromFinish(ActivityRecord activity) {
final Task task = activity.getTask();
- if (activity == task.getRootActivity()
- && activity == task.getTopNonFinishingActivity()
- && task.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE_PRIV
- && isRootTask(task)) {
- Slog.i(TAG, "Not finishing task in lock task mode");
- showLockTaskToast();
- return true;
+ if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV || !isRootTask(task)) {
+ return false;
}
- return false;
+
+ final ActivityRecord taskTop = task.getTopNonFinishingActivity();
+ final ActivityRecord taskRoot = task.getRootActivity();
+ // If task has more than one Activity, verify if there's only adjacent TaskFragments that
+ // should be finish together in the Task.
+ if (activity != taskRoot || activity != taskTop) {
+ final TaskFragment taskFragment = activity.getTaskFragment();
+ final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
+ if (taskFragment.asTask() != null
+ || !taskFragment.isDelayLastActivityRemoval()
+ || adjacentTaskFragment == null) {
+ // Don't block activity from finishing if the TaskFragment don't have any adjacent
+ // TaskFragment, or it won't finish together with its adjacent TaskFragment.
+ return false;
+ }
+
+ final boolean hasOtherActivityInTaskFragment =
+ taskFragment.getActivity(a -> !a.finishing && a != activity) != null;
+ if (hasOtherActivityInTaskFragment) {
+ // Don't block activity from finishing if there's other Activity in the same
+ // TaskFragment.
+ return false;
+ }
+
+ final boolean hasOtherActivityInTask = task.getActivity(a -> !a.finishing
+ && a != activity && a.getTaskFragment() != adjacentTaskFragment) != null;
+ if (hasOtherActivityInTask) {
+ // Do not block activity from finishing if there are another running activities
+ // after the current and adjacent TaskFragments are removed. Note that we don't
+ // check activities in adjacent TaskFragment because it will be finished together
+ // with TaskFragment regardless of numbers of activities.
+ return false;
+ }
+ }
+
+ Slog.i(TAG, "Not finishing task in lock task mode");
+ showLockTaskToast();
+ return true;
}
/**
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index c845dca57c18..97cb512455f6 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -240,7 +240,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
/**
* Whether to delay the last activity of TaskFragment being immediately removed while finishing.
* This should only be set on a embedded TaskFragment, where the organizer can have the
- * opportunity to perform other actions or animations.
+ * opportunity to perform animations and finishing the adjacent TaskFragment.
*/
private boolean mDelayLastActivityRemoval;
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index f72f2cc156ec..f5eb0a7a34d3 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -35,6 +35,7 @@ cc_library_static {
"com_android_server_am_BatteryStatsService.cpp",
"com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
"com_android_server_ConsumerIrService.cpp",
+ "com_android_server_companion_virtual_InputController.cpp",
"com_android_server_devicepolicy_CryptoTestHelper.cpp",
"com_android_server_connectivity_Vpn.cpp",
"com_android_server_gpu_GpuService.cpp",
diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
new file mode 100644
index 000000000000..43018a900f4c
--- /dev/null
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -0,0 +1,455 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputController"
+
+#include <android-base/unique_fd.h>
+#include <android/input.h>
+#include <android/keycodes.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/uinput.h>
+#include <math.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <utils/Log.h>
+
+#include <map>
+#include <string>
+
+namespace android {
+
+enum class DeviceType {
+ KEYBOARD,
+ MOUSE,
+ TOUCHSCREEN,
+};
+
+enum class UinputAction {
+ RELEASE = 0,
+ PRESS = 1,
+ MOVE = 2,
+ CANCEL = 3,
+};
+
+static std::map<int, UinputAction> BUTTON_ACTION_MAPPING = {
+ {AMOTION_EVENT_ACTION_BUTTON_PRESS, UinputAction::PRESS},
+ {AMOTION_EVENT_ACTION_BUTTON_RELEASE, UinputAction::RELEASE},
+};
+
+static std::map<int, UinputAction> KEY_ACTION_MAPPING = {
+ {AKEY_EVENT_ACTION_DOWN, UinputAction::PRESS},
+ {AKEY_EVENT_ACTION_UP, UinputAction::RELEASE},
+};
+
+static std::map<int, UinputAction> TOUCH_ACTION_MAPPING = {
+ {AMOTION_EVENT_ACTION_DOWN, UinputAction::PRESS},
+ {AMOTION_EVENT_ACTION_UP, UinputAction::RELEASE},
+ {AMOTION_EVENT_ACTION_MOVE, UinputAction::MOVE},
+ {AMOTION_EVENT_ACTION_CANCEL, UinputAction::CANCEL},
+};
+
+// Button code mapping from https://source.android.com/devices/input/touch-devices
+static std::map<int, int> BUTTON_CODE_MAPPING = {
+ {AMOTION_EVENT_BUTTON_PRIMARY, BTN_LEFT}, {AMOTION_EVENT_BUTTON_SECONDARY, BTN_RIGHT},
+ {AMOTION_EVENT_BUTTON_TERTIARY, BTN_MIDDLE}, {AMOTION_EVENT_BUTTON_BACK, BTN_BACK},
+ {AMOTION_EVENT_BUTTON_FORWARD, BTN_FORWARD},
+};
+
+// Tool type mapping from https://source.android.com/devices/input/touch-devices
+static std::map<int, int> TOOL_TYPE_MAPPING = {
+ {AMOTION_EVENT_TOOL_TYPE_FINGER, MT_TOOL_FINGER},
+ {AMOTION_EVENT_TOOL_TYPE_PALM, MT_TOOL_PALM},
+};
+
+// Keycode mapping from https://source.android.com/devices/input/keyboard-devices
+static std::map<int, int> KEY_CODE_MAPPING = {
+ {AKEYCODE_0, KEY_0},
+ {AKEYCODE_1, KEY_1},
+ {AKEYCODE_2, KEY_2},
+ {AKEYCODE_3, KEY_3},
+ {AKEYCODE_4, KEY_4},
+ {AKEYCODE_5, KEY_5},
+ {AKEYCODE_6, KEY_6},
+ {AKEYCODE_7, KEY_7},
+ {AKEYCODE_8, KEY_8},
+ {AKEYCODE_9, KEY_9},
+ {AKEYCODE_A, KEY_A},
+ {AKEYCODE_B, KEY_B},
+ {AKEYCODE_C, KEY_C},
+ {AKEYCODE_D, KEY_D},
+ {AKEYCODE_E, KEY_E},
+ {AKEYCODE_F, KEY_F},
+ {AKEYCODE_G, KEY_G},
+ {AKEYCODE_H, KEY_H},
+ {AKEYCODE_I, KEY_I},
+ {AKEYCODE_J, KEY_J},
+ {AKEYCODE_K, KEY_K},
+ {AKEYCODE_L, KEY_L},
+ {AKEYCODE_M, KEY_M},
+ {AKEYCODE_N, KEY_N},
+ {AKEYCODE_O, KEY_O},
+ {AKEYCODE_P, KEY_P},
+ {AKEYCODE_Q, KEY_Q},
+ {AKEYCODE_R, KEY_R},
+ {AKEYCODE_S, KEY_S},
+ {AKEYCODE_T, KEY_T},
+ {AKEYCODE_U, KEY_U},
+ {AKEYCODE_V, KEY_V},
+ {AKEYCODE_W, KEY_W},
+ {AKEYCODE_X, KEY_X},
+ {AKEYCODE_Y, KEY_Y},
+ {AKEYCODE_Z, KEY_Z},
+ {AKEYCODE_GRAVE, KEY_GRAVE},
+ {AKEYCODE_MINUS, KEY_MINUS},
+ {AKEYCODE_EQUALS, KEY_EQUAL},
+ {AKEYCODE_LEFT_BRACKET, KEY_LEFTBRACE},
+ {AKEYCODE_RIGHT_BRACKET, KEY_RIGHTBRACE},
+ {AKEYCODE_BACKSLASH, KEY_BACKSLASH},
+ {AKEYCODE_SEMICOLON, KEY_SEMICOLON},
+ {AKEYCODE_APOSTROPHE, KEY_APOSTROPHE},
+ {AKEYCODE_COMMA, KEY_COMMA},
+ {AKEYCODE_PERIOD, KEY_DOT},
+ {AKEYCODE_SLASH, KEY_SLASH},
+ {AKEYCODE_ALT_LEFT, KEY_LEFTALT},
+ {AKEYCODE_ALT_RIGHT, KEY_RIGHTALT},
+ {AKEYCODE_CTRL_LEFT, KEY_LEFTCTRL},
+ {AKEYCODE_CTRL_RIGHT, KEY_RIGHTCTRL},
+ {AKEYCODE_SHIFT_LEFT, KEY_LEFTSHIFT},
+ {AKEYCODE_SHIFT_RIGHT, KEY_RIGHTSHIFT},
+ {AKEYCODE_META_LEFT, KEY_LEFTMETA},
+ {AKEYCODE_META_RIGHT, KEY_RIGHTMETA},
+ {AKEYCODE_CAPS_LOCK, KEY_CAPSLOCK},
+ {AKEYCODE_SCROLL_LOCK, KEY_SCROLLLOCK},
+ {AKEYCODE_NUM_LOCK, KEY_NUMLOCK},
+ {AKEYCODE_ENTER, KEY_ENTER},
+ {AKEYCODE_TAB, KEY_TAB},
+ {AKEYCODE_SPACE, KEY_SPACE},
+ {AKEYCODE_DPAD_DOWN, KEY_DOWN},
+ {AKEYCODE_DPAD_UP, KEY_UP},
+ {AKEYCODE_DPAD_LEFT, KEY_LEFT},
+ {AKEYCODE_DPAD_RIGHT, KEY_RIGHT},
+ {AKEYCODE_MOVE_END, KEY_END},
+ {AKEYCODE_MOVE_HOME, KEY_HOME},
+ {AKEYCODE_PAGE_DOWN, KEY_PAGEDOWN},
+ {AKEYCODE_PAGE_UP, KEY_PAGEUP},
+ {AKEYCODE_DEL, KEY_BACKSPACE},
+ {AKEYCODE_FORWARD_DEL, KEY_DELETE},
+ {AKEYCODE_INSERT, KEY_INSERT},
+ {AKEYCODE_ESCAPE, KEY_ESC},
+ {AKEYCODE_BREAK, KEY_PAUSE},
+ {AKEYCODE_F1, KEY_F1},
+ {AKEYCODE_F2, KEY_F2},
+ {AKEYCODE_F3, KEY_F3},
+ {AKEYCODE_F4, KEY_F4},
+ {AKEYCODE_F5, KEY_F5},
+ {AKEYCODE_F6, KEY_F6},
+ {AKEYCODE_F7, KEY_F7},
+ {AKEYCODE_F8, KEY_F8},
+ {AKEYCODE_F9, KEY_F9},
+ {AKEYCODE_F10, KEY_F10},
+ {AKEYCODE_F11, KEY_F11},
+ {AKEYCODE_F12, KEY_F12},
+ {AKEYCODE_BACK, KEY_BACK},
+ {AKEYCODE_FORWARD, KEY_FORWARD},
+ {AKEYCODE_NUMPAD_1, KEY_KP1},
+ {AKEYCODE_NUMPAD_2, KEY_KP2},
+ {AKEYCODE_NUMPAD_3, KEY_KP3},
+ {AKEYCODE_NUMPAD_4, KEY_KP4},
+ {AKEYCODE_NUMPAD_5, KEY_KP5},
+ {AKEYCODE_NUMPAD_6, KEY_KP6},
+ {AKEYCODE_NUMPAD_7, KEY_KP7},
+ {AKEYCODE_NUMPAD_8, KEY_KP8},
+ {AKEYCODE_NUMPAD_9, KEY_KP9},
+ {AKEYCODE_NUMPAD_0, KEY_KP0},
+ {AKEYCODE_NUMPAD_ADD, KEY_KPPLUS},
+ {AKEYCODE_NUMPAD_SUBTRACT, KEY_KPMINUS},
+ {AKEYCODE_NUMPAD_MULTIPLY, KEY_KPASTERISK},
+ {AKEYCODE_NUMPAD_DIVIDE, KEY_KPSLASH},
+ {AKEYCODE_NUMPAD_DOT, KEY_KPDOT},
+ {AKEYCODE_NUMPAD_ENTER, KEY_KPENTER},
+ {AKEYCODE_NUMPAD_EQUALS, KEY_KPEQUAL},
+ {AKEYCODE_NUMPAD_COMMA, KEY_KPCOMMA},
+};
+
+/** Creates a new uinput device and assigns a file descriptor. */
+static int openUinput(const char* readableName, jint vendorId, jint productId,
+ DeviceType deviceType, jint screenHeight, jint screenWidth) {
+ android::base::unique_fd fd(TEMP_FAILURE_RETRY(::open("/dev/uinput", O_WRONLY | O_NONBLOCK)));
+ if (fd < 0) {
+ ALOGE("Error creating uinput device: %s", strerror(errno));
+ return -errno;
+ }
+
+ ioctl(fd, UI_SET_EVBIT, EV_KEY);
+ ioctl(fd, UI_SET_EVBIT, EV_SYN);
+ switch (deviceType) {
+ case DeviceType::KEYBOARD:
+ for (const auto& [ignored, keyCode] : KEY_CODE_MAPPING) {
+ ioctl(fd, UI_SET_KEYBIT, keyCode);
+ }
+ break;
+ case DeviceType::MOUSE:
+ ioctl(fd, UI_SET_EVBIT, EV_REL);
+ ioctl(fd, UI_SET_KEYBIT, BTN_LEFT);
+ ioctl(fd, UI_SET_KEYBIT, BTN_RIGHT);
+ ioctl(fd, UI_SET_KEYBIT, BTN_MIDDLE);
+ ioctl(fd, UI_SET_KEYBIT, BTN_BACK);
+ ioctl(fd, UI_SET_KEYBIT, BTN_FORWARD);
+ ioctl(fd, UI_SET_RELBIT, REL_X);
+ ioctl(fd, UI_SET_RELBIT, REL_Y);
+ ioctl(fd, UI_SET_RELBIT, REL_WHEEL);
+ ioctl(fd, UI_SET_RELBIT, REL_HWHEEL);
+ break;
+ case DeviceType::TOUCHSCREEN:
+ ioctl(fd, UI_SET_EVBIT, EV_ABS);
+ ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH);
+ ioctl(fd, UI_SET_ABSBIT, ABS_MT_SLOT);
+ ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_X);
+ ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y);
+ ioctl(fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID);
+ ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOOL_TYPE);
+ ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOUCH_MAJOR);
+ ioctl(fd, UI_SET_ABSBIT, ABS_MT_PRESSURE);
+ ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT);
+ }
+
+ int version;
+ if (ioctl(fd, UI_GET_VERSION, &version) == 0 && version >= 5) {
+ uinput_setup setup;
+ memset(&setup, 0, sizeof(setup));
+ strlcpy(setup.name, readableName, UINPUT_MAX_NAME_SIZE);
+ setup.id.version = 1;
+ setup.id.bustype = BUS_VIRTUAL;
+ setup.id.vendor = vendorId;
+ setup.id.product = productId;
+ if (deviceType == DeviceType::TOUCHSCREEN) {
+ uinput_abs_setup xAbsSetup;
+ xAbsSetup.code = ABS_MT_POSITION_X;
+ xAbsSetup.absinfo.maximum = screenWidth - 1;
+ xAbsSetup.absinfo.minimum = 0;
+ ioctl(fd, UI_ABS_SETUP, xAbsSetup);
+ uinput_abs_setup yAbsSetup;
+ yAbsSetup.code = ABS_MT_POSITION_Y;
+ yAbsSetup.absinfo.maximum = screenHeight - 1;
+ yAbsSetup.absinfo.minimum = 0;
+ ioctl(fd, UI_ABS_SETUP, yAbsSetup);
+ uinput_abs_setup majorAbsSetup;
+ majorAbsSetup.code = ABS_MT_TOUCH_MAJOR;
+ majorAbsSetup.absinfo.maximum = screenWidth - 1;
+ majorAbsSetup.absinfo.minimum = 0;
+ ioctl(fd, UI_ABS_SETUP, majorAbsSetup);
+ uinput_abs_setup pressureAbsSetup;
+ pressureAbsSetup.code = ABS_MT_PRESSURE;
+ pressureAbsSetup.absinfo.maximum = 255;
+ pressureAbsSetup.absinfo.minimum = 0;
+ ioctl(fd, UI_ABS_SETUP, pressureAbsSetup);
+ }
+ if (ioctl(fd, UI_DEV_SETUP, &setup) != 0) {
+ ALOGE("Error creating uinput device: %s", strerror(errno));
+ return -errno;
+ }
+ } else {
+ // UI_DEV_SETUP was not introduced until version 5. Try setting up manually.
+ uinput_user_dev fallback;
+ memset(&fallback, 0, sizeof(fallback));
+ strlcpy(fallback.name, readableName, UINPUT_MAX_NAME_SIZE);
+ fallback.id.version = 1;
+ fallback.id.bustype = BUS_VIRTUAL;
+ fallback.id.vendor = vendorId;
+ fallback.id.product = productId;
+ if (deviceType == DeviceType::TOUCHSCREEN) {
+ fallback.absmin[ABS_MT_POSITION_X] = 0;
+ fallback.absmax[ABS_MT_POSITION_X] = screenWidth - 1;
+ fallback.absmin[ABS_MT_POSITION_Y] = 0;
+ fallback.absmax[ABS_MT_POSITION_Y] = screenHeight - 1;
+ fallback.absmin[ABS_MT_TOUCH_MAJOR] = 0;
+ fallback.absmax[ABS_MT_TOUCH_MAJOR] = screenWidth - 1;
+ fallback.absmin[ABS_MT_PRESSURE] = 0;
+ fallback.absmax[ABS_MT_PRESSURE] = 255;
+ }
+ if (TEMP_FAILURE_RETRY(write(fd, &fallback, sizeof(fallback))) != sizeof(fallback)) {
+ ALOGE("Error creating uinput device: %s", strerror(errno));
+ return -errno;
+ }
+ }
+
+ if (ioctl(fd, UI_DEV_CREATE) != 0) {
+ ALOGE("Error creating uinput device: %s", strerror(errno));
+ return -errno;
+ }
+
+ return fd.release();
+}
+
+static int openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productId,
+ DeviceType deviceType, int screenHeight, int screenWidth) {
+ ScopedUtfChars readableName(env, name);
+ return openUinput(readableName.c_str(), vendorId, productId, deviceType, screenHeight,
+ screenWidth);
+}
+
+static int nativeOpenUinputKeyboard(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
+ jint productId) {
+ return openUinputJni(env, name, vendorId, productId, DeviceType::KEYBOARD, /* screenHeight */ 0,
+ /* screenWidth */ 0);
+}
+
+static int nativeOpenUinputMouse(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
+ jint productId) {
+ return openUinputJni(env, name, vendorId, productId, DeviceType::MOUSE, /* screenHeight */ 0,
+ /* screenWidth */ 0);
+}
+
+static int nativeOpenUinputTouchscreen(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
+ jint productId, jint height, jint width) {
+ return openUinputJni(env, name, vendorId, productId, DeviceType::TOUCHSCREEN, height, width);
+}
+
+static bool nativeCloseUinput(JNIEnv* env, jobject thiz, jint fd) {
+ ioctl(fd, UI_DEV_DESTROY);
+ return close(fd);
+}
+
+static bool writeInputEvent(int fd, uint16_t type, uint16_t code, int32_t value) {
+ struct input_event ev = {.type = type, .code = code, .value = value};
+ return TEMP_FAILURE_RETRY(write(fd, &ev, sizeof(struct input_event))) == sizeof(ev);
+}
+
+static bool nativeWriteKeyEvent(JNIEnv* env, jobject thiz, jint fd, jint androidKeyCode,
+ jint action) {
+ auto keyCodeIterator = KEY_CODE_MAPPING.find(androidKeyCode);
+ if (keyCodeIterator == KEY_CODE_MAPPING.end()) {
+ ALOGE("No supportive native keycode for androidKeyCode %d", androidKeyCode);
+ return false;
+ }
+ auto actionIterator = KEY_ACTION_MAPPING.find(action);
+ if (actionIterator == KEY_ACTION_MAPPING.end()) {
+ return false;
+ }
+ if (!writeInputEvent(fd, EV_KEY, static_cast<uint16_t>(keyCodeIterator->second),
+ static_cast<int32_t>(actionIterator->second))) {
+ return false;
+ }
+ if (!writeInputEvent(fd, EV_SYN, SYN_REPORT, 0)) {
+ return false;
+ }
+ return true;
+}
+
+static bool nativeWriteButtonEvent(JNIEnv* env, jobject thiz, jint fd, jint buttonCode,
+ jint action) {
+ auto buttonCodeIterator = BUTTON_CODE_MAPPING.find(buttonCode);
+ if (buttonCodeIterator == BUTTON_CODE_MAPPING.end()) {
+ return false;
+ }
+ auto actionIterator = BUTTON_ACTION_MAPPING.find(action);
+ if (actionIterator == BUTTON_ACTION_MAPPING.end()) {
+ return false;
+ }
+ if (!writeInputEvent(fd, EV_KEY, static_cast<uint16_t>(buttonCodeIterator->second),
+ static_cast<int32_t>(actionIterator->second))) {
+ return false;
+ }
+ if (!writeInputEvent(fd, EV_SYN, SYN_REPORT, 0)) {
+ return false;
+ }
+ return true;
+}
+
+static bool nativeWriteTouchEvent(JNIEnv* env, jobject thiz, jint fd, jint pointerId, jint toolType,
+ jint action, jfloat locationX, jfloat locationY, jfloat pressure,
+ jfloat majorAxisSize) {
+ if (!writeInputEvent(fd, EV_ABS, ABS_MT_SLOT, pointerId)) {
+ return false;
+ }
+ auto toolTypeIterator = TOOL_TYPE_MAPPING.find(toolType);
+ if (toolTypeIterator == TOOL_TYPE_MAPPING.end()) {
+ return false;
+ }
+ if (toolType != -1) {
+ if (!writeInputEvent(fd, EV_ABS, ABS_MT_TOOL_TYPE,
+ static_cast<int32_t>(toolTypeIterator->second))) {
+ return false;
+ }
+ }
+ auto actionIterator = TOUCH_ACTION_MAPPING.find(action);
+ if (actionIterator == TOUCH_ACTION_MAPPING.end()) {
+ return false;
+ }
+ UinputAction uinputAction = actionIterator->second;
+ if (uinputAction == UinputAction::PRESS || uinputAction == UinputAction::RELEASE) {
+ if (!writeInputEvent(fd, EV_KEY, BTN_TOUCH, static_cast<int32_t>(uinputAction))) {
+ return false;
+ }
+ if (!writeInputEvent(fd, EV_ABS, ABS_MT_TRACKING_ID,
+ static_cast<int32_t>(uinputAction == UinputAction::PRESS ? pointerId
+ : -1))) {
+ return false;
+ }
+ }
+ if (!writeInputEvent(fd, EV_ABS, ABS_MT_POSITION_X, locationX)) {
+ return false;
+ }
+ if (!writeInputEvent(fd, EV_ABS, ABS_MT_POSITION_Y, locationY)) {
+ return false;
+ }
+ if (!isnan(pressure)) {
+ if (!writeInputEvent(fd, EV_ABS, ABS_MT_PRESSURE, pressure)) {
+ return false;
+ }
+ }
+ if (!isnan(majorAxisSize)) {
+ if (!writeInputEvent(fd, EV_ABS, ABS_MT_TOUCH_MAJOR, majorAxisSize)) {
+ return false;
+ }
+ }
+ return writeInputEvent(fd, EV_SYN, SYN_REPORT, 0);
+}
+
+static bool nativeWriteRelativeEvent(JNIEnv* env, jobject thiz, jint fd, jfloat relativeX,
+ jfloat relativeY) {
+ return writeInputEvent(fd, EV_REL, REL_X, relativeX) &&
+ writeInputEvent(fd, EV_REL, REL_Y, relativeY) &&
+ writeInputEvent(fd, EV_SYN, SYN_REPORT, 0);
+}
+
+static bool nativeWriteScrollEvent(JNIEnv* env, jobject thiz, jint fd, jfloat xAxisMovement,
+ jfloat yAxisMovement) {
+ return writeInputEvent(fd, EV_REL, REL_HWHEEL, xAxisMovement) &&
+ writeInputEvent(fd, EV_REL, REL_WHEEL, yAxisMovement) &&
+ writeInputEvent(fd, EV_SYN, SYN_REPORT, 0);
+}
+
+static JNINativeMethod methods[] = {
+ {"nativeOpenUinputKeyboard", "(Ljava/lang/String;II)I", (void*)nativeOpenUinputKeyboard},
+ {"nativeOpenUinputMouse", "(Ljava/lang/String;II)I", (void*)nativeOpenUinputMouse},
+ {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IIII)I",
+ (void*)nativeOpenUinputTouchscreen},
+ {"nativeCloseUinput", "(I)Z", (void*)nativeCloseUinput},
+ {"nativeWriteKeyEvent", "(III)Z", (void*)nativeWriteKeyEvent},
+ {"nativeWriteButtonEvent", "(III)Z", (void*)nativeWriteButtonEvent},
+ {"nativeWriteTouchEvent", "(IIIIFFFF)Z", (void*)nativeWriteTouchEvent},
+ {"nativeWriteRelativeEvent", "(IFF)Z", (void*)nativeWriteRelativeEvent},
+ {"nativeWriteScrollEvent", "(IFF)Z", (void*)nativeWriteScrollEvent},
+};
+
+int register_android_server_companion_virtual_InputController(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/server/companion/virtual/InputController",
+ methods, NELEM(methods));
+}
+
+} // namespace android \ No newline at end of file
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index ff61abc4ff7f..d339ef1154c5 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -63,6 +63,7 @@ int register_android_server_FaceService(JNIEnv* env);
int register_android_server_GpuService(JNIEnv* env);
int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env);
int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env);
+int register_android_server_companion_virtual_InputController(JNIEnv* env);
};
using namespace android;
@@ -119,5 +120,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
register_android_server_GpuService(env);
register_android_server_stats_pull_StatsPullAtomService(env);
register_android_server_sensor_SensorService(vm, env);
+ register_android_server_companion_virtual_InputController(env);
return JNI_VERSION_1_4;
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index d1d7cc6422c3..d9dbf48f969f 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -19,6 +19,7 @@ package com.android.server.pm.test.verify.domain
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
+import android.content.pm.SigningDetails
import android.content.pm.parsing.component.ParsedActivityImpl
import android.content.pm.parsing.component.ParsedIntentInfoImpl
import android.content.pm.verify.domain.DomainVerificationManager
@@ -26,6 +27,7 @@ import android.content.pm.verify.domain.DomainVerificationState
import android.os.Build
import android.os.Process
import android.util.ArraySet
+import android.util.IndentingPrintWriter
import android.util.SparseArray
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.pm.parsing.pkg.AndroidPackage
@@ -46,6 +48,7 @@ import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.anyString
import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
import org.mockito.Mockito.verifyNoMoreInteractions
import java.util.UUID
import java.util.concurrent.atomic.AtomicBoolean
@@ -204,6 +207,14 @@ class DomainVerificationEnforcerTest {
service(Type.QUERENT, "getInfo") {
getDomainVerificationInfo(it.targetPackageName)
},
+ service(Type.QUERENT, "printState") {
+ printState(mock(IndentingPrintWriter::class.java), null, null)
+ },
+ service(Type.QUERENT, "printStateInternal") {
+ printState(mock(IndentingPrintWriter::class.java), null, null) {
+ mockPkgState(it, UUID.randomUUID())
+ }
+ },
service(Type.VERIFIER, "setStatus") {
setDomainVerificationStatus(
it.targetDomainSetId,
@@ -311,6 +322,7 @@ class DomainVerificationEnforcerTest {
}
)
}
+ whenever(signingDetails) { SigningDetails.UNKNOWN }
}
fun mockPkgState(packageName: String, domainSetId: UUID) =
@@ -327,6 +339,7 @@ class DomainVerificationEnforcerTest {
}
}
whenever(isSystem) { false }
+ whenever(signingDetails) { SigningDetails.UNKNOWN }
}
}
@@ -794,8 +807,12 @@ class DomainVerificationEnforcerTest {
}
val valueAsInt = value as? Int
- if (valueAsInt != null && valueAsInt == DomainVerificationManager.STATUS_OK) {
- throw AssertionError("Expected call to return false, was $value")
+ if (valueAsInt != null) {
+ if (valueAsInt == DomainVerificationManager.STATUS_OK) {
+ throw AssertionError("Expected call to return false, was $value")
+ }
+ } else {
+ throw AssertionError("Expected call to fail")
}
} catch (e: SecurityException) {
} catch (e: PackageManager.NameNotFoundException) {
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index e3c60fdfc697..c3a364e723fb 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -32,6 +32,7 @@ android_test {
"services.appwidget",
"services.autofill",
"services.backup",
+ "services.companion",
"services.core",
"services.devicepolicy",
"services.net",
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index f92b872e1d26..28c1c815508f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -585,8 +585,8 @@ public class AbstractAccessibilityServiceConnectionTest {
doAnswer((invocation) -> {
((Region) invocation.getArguments()[1]).set(region);
return null;
- }).when(mMockMagnificationProcessor).getMagnificationRegion(eq(displayId),
- any(), anyBoolean());
+ }).when(mMockMagnificationProcessor).getFullscreenMagnificationRegion(eq(displayId), any(),
+ anyBoolean());
when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
final Region result = mServiceConnection.getMagnificationRegion(displayId);
@@ -620,7 +620,8 @@ public class AbstractAccessibilityServiceConnectionTest {
@Test
public void resetMagnification() {
final int displayId = 1;
- when(mMockMagnificationProcessor.reset(displayId, true)).thenReturn(true);
+ when(mMockMagnificationProcessor.resetFullscreenMagnification(displayId, true)).thenReturn(
+ true);
final boolean result = mServiceConnection.resetMagnification(displayId, true);
assertThat(result, is(true));
@@ -629,7 +630,8 @@ public class AbstractAccessibilityServiceConnectionTest {
@Test
public void resetMagnification_cantControlMagnification_returnFalse() {
final int displayId = 1;
- when(mMockMagnificationProcessor.reset(displayId, true)).thenReturn(true);
+ when(mMockMagnificationProcessor.resetFullscreenMagnification(displayId, true)).thenReturn(
+ true);
when(mMockSecurityPolicy.canControlMagnification(mServiceConnection)).thenReturn(false);
final boolean result = mServiceConnection.resetMagnification(displayId, true);
@@ -639,7 +641,8 @@ public class AbstractAccessibilityServiceConnectionTest {
@Test
public void resetMagnification_serviceNotBelongCurrentUser_returnFalse() {
final int displayId = 1;
- when(mMockMagnificationProcessor.reset(displayId, true)).thenReturn(true);
+ when(mMockMagnificationProcessor.resetFullscreenMagnification(displayId, true)).thenReturn(
+ true);
when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
final boolean result = mServiceConnection.resetMagnification(displayId, true);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
index 9ebec981fa21..621507e2bfc8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
@@ -98,7 +98,7 @@ public class MagnificationProcessorTest {
.setScale(TEST_SCALE).build();
setMagnificationActivated(TEST_DISPLAY, config);
- float scale = mMagnificationProcessor.getScale(TEST_DISPLAY);
+ float scale = mMagnificationProcessor.getMagnificationConfig(TEST_DISPLAY).getScale();
assertEquals(scale, TEST_SCALE, 0);
}
@@ -111,20 +111,19 @@ public class MagnificationProcessorTest {
setMagnificationActivated(TEST_DISPLAY, config);
float centerX = mMagnificationProcessor.getCenterX(
- TEST_DISPLAY, /* canControlMagnification= */true);
+ TEST_DISPLAY, /* canControlMagnification= */true);
assertEquals(centerX, TEST_CENTER_X, 0);
}
@Test
- public void getCenterX_canControlWindowMagnification_returnCenterX() {
+ public void getCenterX_controlWindowMagnification_returnCenterX() {
final MagnificationConfig config = new MagnificationConfig.Builder()
.setMode(MAGNIFICATION_MODE_WINDOW)
.setCenterX(TEST_CENTER_X).build();
setMagnificationActivated(TEST_DISPLAY, config);
- float centerX = mMagnificationProcessor.getCenterX(
- TEST_DISPLAY, /* canControlMagnification= */true);
+ float centerX = mMagnificationProcessor.getMagnificationConfig(TEST_DISPLAY).getCenterX();
assertEquals(centerX, TEST_CENTER_X, 0);
}
@@ -143,14 +142,13 @@ public class MagnificationProcessorTest {
}
@Test
- public void getCenterY_canControlWindowMagnification_returnCenterY() {
+ public void getCenterY_controlWindowMagnification_returnCenterY() {
final MagnificationConfig config = new MagnificationConfig.Builder()
.setMode(MAGNIFICATION_MODE_WINDOW)
.setCenterY(TEST_CENTER_Y).build();
setMagnificationActivated(TEST_DISPLAY, config);
- float centerY = mMagnificationProcessor.getCenterY(
- TEST_DISPLAY, /* canControlMagnification= */false);
+ float centerY = mMagnificationProcessor.getMagnificationConfig(TEST_DISPLAY).getCenterY();
assertEquals(centerY, TEST_CENTER_Y, 0);
}
@@ -159,7 +157,7 @@ public class MagnificationProcessorTest {
public void getMagnificationRegion_canControlFullscreenMagnification_returnRegion() {
final Region region = new Region(10, 20, 100, 200);
setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN);
- mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY,
+ mMagnificationProcessor.getFullscreenMagnificationRegion(TEST_DISPLAY,
region, /* canControlMagnification= */true);
verify(mMockFullScreenMagnificationController).getMagnificationRegion(eq(TEST_DISPLAY),
@@ -167,17 +165,6 @@ public class MagnificationProcessorTest {
}
@Test
- public void getMagnificationRegion_canControlWindowMagnification_returnRegion() {
- final Region region = new Region(10, 20, 100, 200);
- setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_WINDOW);
- mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY,
- region, /* canControlMagnification= */true);
-
- verify(mMockWindowMagnificationManager).getMagnificationSourceBounds(eq(TEST_DISPLAY),
- eq(region));
- }
-
- @Test
public void getMagnificationRegion_fullscreenModeNotRegistered_shouldRegisterThenUnregister() {
final Region region = new Region(10, 20, 100, 200);
setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN);
@@ -188,7 +175,7 @@ public class MagnificationProcessorTest {
any());
final Region result = new Region();
- mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY,
+ mMagnificationProcessor.getFullscreenMagnificationRegion(TEST_DISPLAY,
result, /* canControlMagnification= */true);
assertEquals(region, result);
verify(mMockFullScreenMagnificationController).register(TEST_DISPLAY);
@@ -237,7 +224,7 @@ public class MagnificationProcessorTest {
public void reset_fullscreenMagnificationActivated() {
setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN);
- mMagnificationProcessor.reset(TEST_DISPLAY, /* animate= */false);
+ mMagnificationProcessor.resetFullscreenMagnification(TEST_DISPLAY, /* animate= */false);
verify(mMockFullScreenMagnificationController).reset(TEST_DISPLAY, false);
}
@@ -246,7 +233,7 @@ public class MagnificationProcessorTest {
public void reset_windowMagnificationActivated() {
setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_WINDOW);
- mMagnificationProcessor.reset(TEST_DISPLAY, /* animate= */false);
+ mMagnificationProcessor.resetCurrentMagnification(TEST_DISPLAY, /* animate= */false);
verify(mMockWindowMagnificationManager).reset(TEST_DISPLAY);
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
new file mode 100644
index 000000000000..c7c0756bc0d0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.assertThrows;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.graphics.Point;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.VirtualKeyEvent;
+import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseRelativeEvent;
+import android.hardware.input.VirtualMouseScrollEvent;
+import android.hardware.input.VirtualTouchEvent;
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+import android.view.KeyEvent;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class VirtualDeviceManagerServiceTest {
+
+ private static final String DEVICE_NAME = "device name";
+ private static final int DISPLAY_ID = 2;
+ private static final int PRODUCT_ID = 10;
+ private static final int VENDOR_ID = 5;
+ private static final int HEIGHT = 1800;
+ private static final int WIDTH = 900;
+ private static final Binder BINDER = new Binder("binder");
+
+ private Context mContext;
+ private VirtualDeviceImpl mDeviceImpl;
+ private InputController mInputController;
+ @Mock
+ private InputController.NativeWrapper mNativeWrapperMock;
+ @Mock
+ private DisplayManagerInternal mDisplayManagerInternalMock;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+ LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+
+ mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+ doNothing().when(mContext).enforceCallingOrSelfPermission(
+ eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
+ mInputController = new InputController(new Object(), mNativeWrapperMock);
+ mDeviceImpl = new VirtualDeviceImpl(mContext,
+ /* association info */ null, new Binder(), /* uid */ 0, mInputController,
+ (int associationId) -> {});
+ }
+
+ @Test
+ public void createVirtualKeyboard_noDisplay_failsSecurityException() {
+ assertThrows(
+ SecurityException.class,
+ () -> mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
+ PRODUCT_ID, BINDER));
+ }
+
+ @Test
+ public void createVirtualMouse_noDisplay_failsSecurityException() {
+ assertThrows(
+ SecurityException.class,
+ () -> mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
+ PRODUCT_ID, BINDER));
+ }
+
+ @Test
+ public void createVirtualTouchscreen_noDisplay_failsSecurityException() {
+ assertThrows(
+ SecurityException.class,
+ () -> mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME,
+ VENDOR_ID, PRODUCT_ID, BINDER, new Point(WIDTH, HEIGHT)));
+ }
+
+ @Test
+ public void createVirtualKeyboard_noPermission_failsSecurityException() {
+ mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+ doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
+ eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
+ assertThrows(
+ SecurityException.class,
+ () -> mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
+ PRODUCT_ID, BINDER));
+ }
+
+ @Test
+ public void createVirtualMouse_noPermission_failsSecurityException() {
+ mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+ doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
+ eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
+ assertThrows(
+ SecurityException.class,
+ () -> mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
+ PRODUCT_ID, BINDER));
+ }
+
+ @Test
+ public void createVirtualTouchscreen_noPermission_failsSecurityException() {
+ mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+ doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
+ eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
+ assertThrows(
+ SecurityException.class,
+ () -> mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME,
+ VENDOR_ID, PRODUCT_ID, BINDER, new Point(WIDTH, HEIGHT)));
+ }
+
+ @Test
+ public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() {
+ mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+ mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
+ BINDER);
+ assertWithMessage("Virtual keyboard should register fd when the display matches")
+ .that(mInputController.mInputDeviceFds).isNotEmpty();
+ verify(mNativeWrapperMock).openUinputKeyboard(DEVICE_NAME, VENDOR_ID, PRODUCT_ID);
+ }
+
+ @Test
+ public void createVirtualMouse_hasDisplay_obtainFileDescriptor() {
+ mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+ mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
+ BINDER);
+ assertWithMessage("Virtual keyboard should register fd when the display matches")
+ .that(mInputController.mInputDeviceFds).isNotEmpty();
+ verify(mNativeWrapperMock).openUinputMouse(DEVICE_NAME, VENDOR_ID, PRODUCT_ID);
+ }
+
+ @Test
+ public void createVirtualTouchscreen_hasDisplay_obtainFileDescriptor() {
+ mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+ mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
+ BINDER, new Point(WIDTH, HEIGHT));
+ assertWithMessage("Virtual keyboard should register fd when the display matches")
+ .that(mInputController.mInputDeviceFds).isNotEmpty();
+ verify(mNativeWrapperMock).openUinputTouchscreen(DEVICE_NAME, VENDOR_ID, PRODUCT_ID, HEIGHT,
+ WIDTH);
+ }
+
+ @Test
+ public void sendKeyEvent_noFd() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder()
+ .setKeyCode(KeyEvent.KEYCODE_A)
+ .setAction(VirtualKeyEvent.ACTION_DOWN).build()));
+ }
+
+ @Test
+ public void sendKeyEvent_hasFd_writesEvent() {
+ final int fd = 1;
+ final int keyCode = KeyEvent.KEYCODE_A;
+ final int action = VirtualKeyEvent.ACTION_UP;
+ mInputController.mInputDeviceFds.put(BINDER, fd);
+ mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder().setKeyCode(keyCode)
+ .setAction(action).build());
+ verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action);
+ }
+
+ @Test
+ public void sendButtonEvent_noFd() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ mDeviceImpl.sendButtonEvent(BINDER,
+ new VirtualMouseButtonEvent.Builder()
+ .setButtonCode(VirtualMouseButtonEvent.BUTTON_BACK)
+ .setAction(VirtualMouseButtonEvent.ACTION_BUTTON_PRESS)
+ .build()));
+ }
+
+ @Test
+ public void sendButtonEvent_hasFd_writesEvent() {
+ final int fd = 1;
+ final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK;
+ final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
+ mInputController.mInputDeviceFds.put(BINDER, fd);
+ mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder()
+ .setButtonCode(buttonCode)
+ .setAction(action).build());
+ verify(mNativeWrapperMock).writeButtonEvent(fd, buttonCode, action);
+ }
+
+ @Test
+ public void sendRelativeEvent_noFd() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ mDeviceImpl.sendRelativeEvent(BINDER,
+ new VirtualMouseRelativeEvent.Builder().setRelativeX(
+ 0.0f).setRelativeY(0.0f).build()));
+ }
+
+ @Test
+ public void sendRelativeEvent_hasFd_writesEvent() {
+ final int fd = 1;
+ final float x = -0.2f;
+ final float y = 0.7f;
+ mInputController.mInputDeviceFds.put(BINDER, fd);
+ mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder()
+ .setRelativeX(x).setRelativeY(y).build());
+ verify(mNativeWrapperMock).writeRelativeEvent(fd, x, y);
+ }
+
+ @Test
+ public void sendScrollEvent_noFd() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ mDeviceImpl.sendScrollEvent(BINDER,
+ new VirtualMouseScrollEvent.Builder()
+ .setXAxisMovement(-1f)
+ .setYAxisMovement(1f).build()));
+ }
+
+ @Test
+ public void sendScrollEvent_hasFd_writesEvent() {
+ final int fd = 1;
+ final float x = 0.5f;
+ final float y = 1f;
+ mInputController.mInputDeviceFds.put(BINDER, fd);
+ mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder()
+ .setXAxisMovement(x)
+ .setYAxisMovement(y).build());
+ verify(mNativeWrapperMock).writeScrollEvent(fd, x, y);
+ }
+
+ @Test
+ public void sendTouchEvent_noFd() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder()
+ .setX(0.0f)
+ .setY(0.0f)
+ .setAction(VirtualTouchEvent.ACTION_UP)
+ .setPointerId(1)
+ .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER)
+ .build()));
+ }
+
+ @Test
+ public void sendTouchEvent_hasFd_writesEvent_withoutPressureOrMajorAxisSize() {
+ final int fd = 1;
+ final int pointerId = 5;
+ final int toolType = VirtualTouchEvent.TOOL_TYPE_FINGER;
+ final float x = 100.5f;
+ final float y = 200.5f;
+ final int action = VirtualTouchEvent.ACTION_UP;
+ mInputController.mInputDeviceFds.put(BINDER, fd);
+ mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
+ .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType).build());
+ verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN,
+ Float.NaN);
+ }
+
+ @Test
+ public void sendTouchEvent_hasFd_writesEvent() {
+ final int fd = 1;
+ final int pointerId = 5;
+ final int toolType = VirtualTouchEvent.TOOL_TYPE_FINGER;
+ final float x = 100.5f;
+ final float y = 200.5f;
+ final int action = VirtualTouchEvent.ACTION_UP;
+ final float pressure = 1.0f;
+ final float majorAxisSize = 10.0f;
+ mInputController.mInputDeviceFds.put(BINDER, fd);
+ mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
+ .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType)
+ .setPressure(pressure).setMajorAxisSize(majorAxisSize).build());
+ verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, pressure,
+ majorAxisSize);
+ }
+}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 1daa4cfe49bf..c80d35b9e772 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5981,6 +5981,7 @@ public class CarrierConfigManager {
sDefaults.putInt(
KEY_OPPORTUNISTIC_TIME_TO_SCAN_AFTER_CAPABILITY_SWITCH_TO_PRIMARY_LONG,
120000);
+ sDefaults.putAll(ImsServiceEntitlement.getDefaults());
sDefaults.putAll(Gps.getDefaults());
sDefaults.putIntArray(KEY_CDMA_ENHANCED_ROAMING_INDICATOR_FOR_HOME_NETWORK_INT_ARRAY,
new int[] {
diff --git a/telephony/java/android/telephony/ims/RcsClientConfiguration.java b/telephony/java/android/telephony/ims/RcsClientConfiguration.java
index 793c37745de6..c25ace0c6a62 100644
--- a/telephony/java/android/telephony/ims/RcsClientConfiguration.java
+++ b/telephony/java/android/telephony/ims/RcsClientConfiguration.java
@@ -50,6 +50,7 @@ public final class RcsClientConfiguration implements Parcelable {
private String mRcsProfile;
private String mClientVendor;
private String mClientVersion;
+ private boolean mRcsEnabledByUser;
/**
* Create a RcsClientConfiguration object.
@@ -63,14 +64,41 @@ public final class RcsClientConfiguration implements Parcelable {
* @param clientVersion Identifies the RCS client version. Refer to GSMA
* RCC.07 "client_version" parameter.
* Example:client_version=RCSAndrd-1.0
+ * @deprecated Use {@link #RcsClientConfiguration(String, String, String, String, boolean)}
+ * instead. Deprecated prototype assumes that the user setting controlling RCS is enabled.
*/
+ @Deprecated
public RcsClientConfiguration(@NonNull String rcsVersion,
@NonNull @StringRcsProfile String rcsProfile,
@NonNull String clientVendor, @NonNull String clientVersion) {
+ this(rcsVersion, rcsProfile, clientVendor, clientVersion, true);
+ }
+
+ /**
+ * Create a RcsClientConfiguration object.
+ * Default messaging application must pass a valid configuration object
+ * @param rcsVersion The parameter identifies the RCS version supported
+ * by the client. Refer to GSMA RCC.07 "rcs_version" parameter.
+ * @param rcsProfile Identifies a fixed set of RCS services that are
+ * supported by the client. See {@link #RCS_PROFILE_1_0 } or
+ * {@link #RCS_PROFILE_2_3 }
+ * @param clientVendor Identifies the vendor providing the RCS client.
+ * @param clientVersion Identifies the RCS client version. Refer to GSMA
+ * RCC.07 "client_version" parameter.
+ * Example:client_version=RCSAndrd-1.0
+ * @param isRcsEnabledByUser The current user setting for whether or not the user has
+ * enabled or disabled RCS. Please refer to GSMA RCC.07 "rcs_state" parameter for how this
+ * can affect provisioning.
+ */
+ public RcsClientConfiguration(@NonNull String rcsVersion,
+ @NonNull @StringRcsProfile String rcsProfile,
+ @NonNull String clientVendor, @NonNull String clientVersion,
+ boolean isRcsEnabledByUser) {
mRcsVersion = rcsVersion;
mRcsProfile = rcsProfile;
mClientVendor = clientVendor;
mClientVersion = clientVersion;
+ mRcsEnabledByUser = isRcsEnabledByUser;
}
/**
@@ -102,6 +130,18 @@ public final class RcsClientConfiguration implements Parcelable {
}
/**
+ * The current user setting provided by the RCS messaging application that determines
+ * whether or not the user has enabled RCS.
+ * <p>
+ * See GSMA RCC.07 "rcs_state" parameter for more information about how this setting
+ * affects provisioning.
+ * @return true if RCS is enabled by the user, false if RCS is disabled by the user.
+ */
+ public boolean isRcsEnabledByUser() {
+ return mRcsEnabledByUser;
+ }
+
+ /**
* {@link Parcelable#writeToParcel}
*/
@Override
@@ -110,6 +150,7 @@ public final class RcsClientConfiguration implements Parcelable {
out.writeString(mRcsProfile);
out.writeString(mClientVendor);
out.writeString(mClientVersion);
+ out.writeBoolean(mRcsEnabledByUser);
}
/**
@@ -124,8 +165,9 @@ public final class RcsClientConfiguration implements Parcelable {
String rcsProfile = in.readString();
String clientVendor = in.readString();
String clientVersion = in.readString();
+ Boolean rcsEnabledByUser = in.readBoolean();
return new RcsClientConfiguration(rcsVersion, rcsProfile,
- clientVendor, clientVersion);
+ clientVendor, clientVersion, rcsEnabledByUser);
}
@Override
@@ -152,11 +194,13 @@ public final class RcsClientConfiguration implements Parcelable {
return mRcsVersion.equals(other.mRcsVersion) && mRcsProfile.equals(other.mRcsProfile)
&& mClientVendor.equals(other.mClientVendor)
- && mClientVersion.equals(other.mClientVersion);
+ && mClientVersion.equals(other.mClientVersion)
+ && (mRcsEnabledByUser == other.mRcsEnabledByUser);
}
@Override
public int hashCode() {
- return Objects.hash(mRcsVersion, mRcsProfile, mClientVendor, mClientVersion);
+ return Objects.hash(mRcsVersion, mRcsProfile, mClientVendor, mClientVersion,
+ mRcsEnabledByUser);
}
}
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index 7a1c09275a6d..61de3ac2b25e 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -28,13 +28,10 @@ import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
-import android.telephony.CarrierConfigManager;
-import android.telephony.SubscriptionManager;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.ims.aidl.IImsRcsController;
import android.telephony.ims.aidl.IRcsUceControllerCallback;
import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
-import android.telephony.ims.feature.RcsFeature;
import android.util.Log;
import java.lang.annotation.Retention;
@@ -337,6 +334,14 @@ public class RcsUceAdapter {
@SystemApi
public static final int PUBLISH_STATE_OTHER_ERROR = 6;
+ /**
+ * The device is currently trying to publish its capabilities to the network.
+ * @hide
+ */
+ @SystemApi
+ public static final int PUBLISH_STATE_PUBLISHING = 7;
+
+
/**@hide*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "PUBLISH_STATE_", value = {
@@ -345,7 +350,8 @@ public class RcsUceAdapter {
PUBLISH_STATE_VOICE_PROVISION_ERROR,
PUBLISH_STATE_RCS_PROVISION_ERROR,
PUBLISH_STATE_REQUEST_TIMEOUT,
- PUBLISH_STATE_OTHER_ERROR
+ PUBLISH_STATE_OTHER_ERROR,
+ PUBLISH_STATE_PUBLISHING
})
public @interface PublishState {}
@@ -480,9 +486,12 @@ public class RcsUceAdapter {
* <p>
* Be sure to check the availability of this feature using
* {@link ImsRcsManager#isAvailable(int, int)} and ensuring
- * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
- * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is enabled or else
- * this operation will fail with {@link #ERROR_NOT_AVAILABLE} or {@link #ERROR_NOT_ENABLED}.
+ * {@link
+ * android.telephony.ims.feature.RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
+ * {@link
+ * android.telephony.ims.feature.RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is
+ * enabled or else this operation will fail with {@link #ERROR_NOT_AVAILABLE} or
+ * {@link #ERROR_NOT_ENABLED}.
*
* @param contactNumbers A list of numbers that the capabilities are being requested for.
* @param executor The executor that will be used when the request is completed and the
@@ -573,8 +582,10 @@ public class RcsUceAdapter {
* <p>
* Be sure to check the availability of this feature using
* {@link ImsRcsManager#isAvailable(int, int)} and ensuring
- * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
- * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is
+ * {@link
+ * android.telephony.ims.feature.RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
+ * {@link
+ * android.telephony.ims.feature.RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is
* enabled or else this operation will fail with
* {@link #ERROR_NOT_AVAILABLE} or {@link #ERROR_NOT_ENABLED}.
*
@@ -690,7 +701,8 @@ public class RcsUceAdapter {
* Registers a {@link OnPublishStateChangedListener} with the system, which will provide publish
* state updates for the subscription specified in {@link ImsManager@getRcsManager(subid)}.
* <p>
- * Use {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to subscription
+ * Use {@link android.telephony.SubscriptionManager.OnSubscriptionsChangedListener} to listen
+ * to subscription
* changed events and call
* {@link #removeOnPublishStateChangedListener(OnPublishStateChangedListener)} to clean up.
* <p>
@@ -792,7 +804,8 @@ public class RcsUceAdapter {
* cache associated with those contacts as the local cache becomes stale.
* <p>
* This setting will only enable this feature if
- * {@link CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} is also enabled.
+ * {@link android.telephony.CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} is
+ * also enabled.
* <p>
* Note: This setting does not affect whether or not the device publishes its service
* capabilities if the subscription supports presence publication.
@@ -843,7 +856,8 @@ public class RcsUceAdapter {
* session.
* <p>
* This setting will only enable this feature if
- * {@link CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} is also enabled.
+ * {@link android.telephony.CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} is
+ * also enabled.
* <p>
* Note: This setting does not affect whether or not the device publishes its service
* capabilities if the subscription supports presence publication.
diff --git a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
index c3d7325f2e0a..c27fa4fc882d 100644
--- a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
+++ b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
@@ -81,6 +81,26 @@ public class CapabilityExchangeAidlWrapper implements CapabilityExchangeEventLis
}
/**
+ * Receives the status of changes in the publishing connection from ims service
+ * and deliver this callback to the framework.
+ */
+ public void onPublishUpdated(int reasonCode, @NonNull String reasonPhrase,
+ int reasonHeaderCause, @NonNull String reasonHeaderText) throws ImsException {
+ ICapabilityExchangeEventListener listener = mListenerBinder;
+ if (listener == null) {
+ return;
+ }
+ try {
+ listener.onPublishUpdated(reasonCode, reasonPhrase,
+ reasonHeaderCause, reasonHeaderText);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "onPublishUpdated exception: " + e);
+ throw new ImsException("Remote is not available",
+ ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+ }
+ }
+
+ /**
* Receives the callback of the remote capability request from the network and deliver this
* request to the framework.
*/
diff --git a/telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl b/telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl
index 078ac919b75e..c675bc3204f6 100644
--- a/telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl
@@ -31,6 +31,8 @@ import java.util.List;
oneway interface ICapabilityExchangeEventListener {
void onRequestPublishCapabilities(int publishTriggerType);
void onUnpublish();
+ void onPublishUpdated(int reasonCode, String reasonPhrase, int reasonHeaderCause,
+ String reasonHeaderText);
void onRemoteCapabilityRequest(in Uri contactUri,
in List<String> remoteCapabilities, IOptionsRequestCallback cb);
}
diff --git a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
index a3be8dab2891..9293a40d9a33 100644
--- a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
+++ b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
@@ -90,6 +90,30 @@ public interface CapabilityExchangeEventListener {
void onUnpublish() throws ImsException;
/**
+ * Notify the framework that the ImsService has refreshed the PUBLISH
+ * internally, which has resulted in a new PUBLISH result.
+ * <p>
+ * This method must return both SUCCESS (200 OK) and FAILURE (300+) codes in order to
+ * keep the AOSP stack up to date.
+ * @param reasonCode The SIP response code sent from the network.
+ * @param reasonPhrase The optional reason response from the network. If the
+ * network provided no reason with the sip code, the string should be empty.
+ * @param reasonHeaderCause The “cause” parameter of the “reason” header
+ * included in the SIP message.
+ * @param reasonHeaderText The “text” parameter of the “reason” header
+ * included in the SIP message.
+ * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is not
+ * currently connected to the framework. This can happen if the {@link RcsFeature} is not
+ * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received
+ * the {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare
+ * cases when the Telephony stack has crashed.
+ *
+ */
+ default void onPublishUpdated(int reasonCode, @NonNull String reasonPhrase,
+ int reasonHeaderCause, @NonNull String reasonHeaderText) throws ImsException {
+ }
+
+ /**
* Inform the framework of an OPTIONS query from a remote device for this device's UCE
* capabilities.
* <p>
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 866fd2c7394e..ba9584112b75 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -530,6 +530,8 @@ public interface RILConstants {
int RIL_REQUEST_GET_SLICING_CONFIG = 224;
int RIL_REQUEST_ENABLE_VONR = 225;
int RIL_REQUEST_IS_VONR_ENABLED = 226;
+ int RIL_REQUEST_SET_USAGE_SETTING = 227;
+ int RIL_REQUEST_GET_USAGE_SETTING = 228;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index 5d0d718e15c3..3914ef6633b3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -231,7 +231,7 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti
@Test
fun screenLockedStart() {
testSpec.assertLayersStart {
- isVisible(colorFadComponent)
+ isEmpty()
}
}