summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java25
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig10
-rw-r--r--core/java/android/companion/virtual/flags/flags.aconfig7
-rw-r--r--core/java/android/os/ArtModuleServiceManager.java32
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java1
-rw-r--r--media/java/android/media/MediaRouter2.java3
-rw-r--r--packages/SettingsLib/aconfig/settingslib.aconfig10
-rw-r--r--packages/SettingsLib/res/values/config.xml10
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java8
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/Android.bp1
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java11
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt9
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt6
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt9
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt9
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt9
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt9
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ActionableContent.kt46
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt9
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Overlay.kt15
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Scene.kt27
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt9
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt65
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt79
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt124
-rw-r--r--packages/SystemUI/docs/scene.md6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModelTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModelTest.kt)6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt185
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModelTest.kt)30
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModelTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModelTest.kt)6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelTest.kt)4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModelTest.kt)6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt34
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModelTest.kt)14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/UserActionsViewModelTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt)6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelTest.kt)4
-rw-r--r--packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml2
-rw-r--r--packages/SystemUI/res/layout/screen_share_dialog.xml13
-rw-r--r--packages/SystemUI/res/values/dimens.xml1
-rw-r--r--packages/SystemUI/res/values/strings.xml16
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt)8
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt146
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModel.kt)8
-rw-r--r--packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModel.kt)8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModel.kt)8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModel.kt)8
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModel.kt)6
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/UserActionsViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt)6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModel.kt)8
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt158
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt70
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt)4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeUserActionsViewModelKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneActionsViewModelKosmos.kt)6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelKosmos.kt)4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt8
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java3
-rw-r--r--services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java195
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java50
-rw-r--r--services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java4
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java51
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerControllerInterface.java267
-rw-r--r--services/core/java/com/android/server/input/debug/TouchpadDebugView.java139
-rw-r--r--services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java29
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java33
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java9
-rw-r--r--services/core/java/com/android/server/power/ThermalManagerService.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java22
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java33
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java19
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java26
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java2
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt21
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java2
-rw-r--r--tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java292
95 files changed, 1960 insertions, 826 deletions
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 1ddec17e49bb..0f54cb7bc35e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -10476,10 +10476,6 @@ public class DevicePolicyManager {
@WorkerThread
public void setApplicationRestrictions(@Nullable ComponentName admin, String packageName,
Bundle settings) {
- if (!Flags.dmrhSetAppRestrictions()) {
- throwIfParentInstance("setApplicationRestrictions");
- }
-
if (mService != null) {
try {
mService.setApplicationRestrictions(admin, mContext.getPackageName(), packageName,
@@ -11884,9 +11880,6 @@ public class DevicePolicyManager {
@WorkerThread
public @NonNull Bundle getApplicationRestrictions(
@Nullable ComponentName admin, String packageName) {
- if (!Flags.dmrhSetAppRestrictions()) {
- throwIfParentInstance("getApplicationRestrictions");
- }
if (mService != null) {
try {
@@ -14231,21 +14224,11 @@ public class DevicePolicyManager {
*/
public @NonNull DevicePolicyManager getParentProfileInstance(@NonNull ComponentName admin) {
throwIfParentInstance("getParentProfileInstance");
- try {
- if (Flags.dmrhSetAppRestrictions()) {
- UserManager um = mContext.getSystemService(UserManager.class);
- if (!um.isManagedProfile()) {
- throw new SecurityException("The current user does not have a parent profile.");
- }
- } else {
- if (!mService.isManagedProfile(admin)) {
- throw new SecurityException("The current user does not have a parent profile.");
- }
- }
- return new DevicePolicyManager(mContext, mService, true);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ UserManager um = mContext.getSystemService(UserManager.class);
+ if (!um.isManagedProfile()) {
+ throw new SecurityException("The current user does not have a parent profile.");
}
+ return new DevicePolicyManager(mContext, mService, true);
}
/**
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index d9bd77fb3d54..540592ff0b90 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -210,16 +210,6 @@ flag {
}
flag {
- name: "dmrh_set_app_restrictions"
- namespace: "enterprise"
- description: "Allow DMRH to set application restrictions (both on the profile and the parent)"
- bug: "328758346"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "always_persist_do"
namespace: "enterprise"
description: "Always write device_owners2.xml so that migration flags aren't lost"
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index 22a9ccf425c2..297fe8a9e691 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -111,3 +111,10 @@ flag {
description: "Device awareness in power and display APIs"
bug: "285020111"
}
+
+flag {
+ name: "status_bar_and_insets"
+ namespace: "virtual_devices"
+ description: "Allow for status bar and insets on virtual devices"
+ bug: "350007866"
+}
diff --git a/core/java/android/os/ArtModuleServiceManager.java b/core/java/android/os/ArtModuleServiceManager.java
index e0b631d69ca8..995094bb1318 100644
--- a/core/java/android/os/ArtModuleServiceManager.java
+++ b/core/java/android/os/ArtModuleServiceManager.java
@@ -37,10 +37,12 @@ public class ArtModuleServiceManager {
/** A class that exposes the method to obtain each system service. */
public static final class ServiceRegisterer {
@NonNull private final String mServiceName;
+ private final boolean mRetry;
/** @hide */
- public ServiceRegisterer(@NonNull String serviceName) {
+ public ServiceRegisterer(@NonNull String serviceName, boolean retry) {
mServiceName = serviceName;
+ mRetry = retry;
}
/**
@@ -53,27 +55,47 @@ public class ArtModuleServiceManager {
*/
@Nullable
public IBinder waitForService() {
- return ServiceManager.waitForService(mServiceName);
+ if (mRetry) {
+ return ServiceManager.waitForService(mServiceName);
+ }
+ IBinder binder = ServiceManager.getService(mServiceName);
+ for (int remainingTimeMs = 5000; binder == null && remainingTimeMs > 0;
+ remainingTimeMs -= 100) {
+ // There can be a race:
+ // 1. Client A invokes "ctl.start", which starts the service.
+ // 2. Client A gets a service handle from `ServiceManager.getService`.
+ // 3. Client B invokes "ctl.start", which does nothing because the service is
+ // already running.
+ // 4. Client A drops the service handle. The service is notified that there is no
+ // more client at that point, so it shuts down itself.
+ // 5. Client B cannot get a service handle from `ServiceManager.getService` because
+ // the service is shut down.
+ // To address this problem, we invoke "ctl.start" repeatedly.
+ SystemProperties.set("ctl.start", mServiceName);
+ SystemClock.sleep(100);
+ binder = ServiceManager.getService(mServiceName);
+ }
+ return binder;
}
}
/** Returns {@link ServiceRegisterer} for the "artd" service. */
@NonNull
public ServiceRegisterer getArtdServiceRegisterer() {
- return new ServiceRegisterer("artd");
+ return new ServiceRegisterer("artd", true /* retry */);
}
/** Returns {@link ServiceRegisterer} for the "artd_pre_reboot" service. */
@NonNull
@FlaggedApi(Flags.FLAG_USE_ART_SERVICE_V2)
public ServiceRegisterer getArtdPreRebootServiceRegisterer() {
- return new ServiceRegisterer("artd_pre_reboot");
+ return new ServiceRegisterer("artd_pre_reboot", false /* retry */);
}
/** Returns {@link ServiceRegisterer} for the "dexopt_chroot_setup" service. */
@NonNull
@FlaggedApi(Flags.FLAG_USE_ART_SERVICE_V2)
public ServiceRegisterer getDexoptChrootSetupServiceRegisterer() {
- return new ServiceRegisterer("dexopt_chroot_setup");
+ return new ServiceRegisterer("dexopt_chroot_setup", true /* retry */);
}
}
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index ebf87f1d1dba..cab6d8e9774a 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -245,3 +245,10 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enable_desktop_windowing_persistence"
+ namespace: "lse_desktop_experience"
+ description: "Persists the desktop windowing session across reboots."
+ bug: "350456942"
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 972b78f6ca9a..6146ecd9ade6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -831,7 +831,6 @@ public class CompatUIController implements OnDisplaysChangedListener,
*/
static class CompatUIHintsState {
boolean mHasShownSizeCompatHint;
- boolean mHasShownCameraCompatHint;
boolean mHasShownUserAspectRatioSettingsButtonHint;
}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 1930c3d8b9b7..b84990b54bd5 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -288,8 +288,7 @@ public final class MediaRouter2 {
/**
* Returns a proxy MediaRouter2 instance that allows you to control the routing of an app
- * specified by {@code clientPackageName}. Returns {@code null} if the specified package name
- * does not exist.
+ * specified by {@code clientPackageName}.
*
* <p>Proxy MediaRouter2 instances operate differently than regular MediaRouter2 instances:
*
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 2b3862f88c07..34b597ba4486 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -129,3 +129,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "member_device_lea_active_state_sync_fix"
+ namespace: "cross_device_experiences"
+ description: "Gates whether to enable fix for member device active state sync on lea profile"
+ bug: "364201289"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml
index 68b81db1d9c0..3c3de044cc4e 100644
--- a/packages/SettingsLib/res/values/config.xml
+++ b/packages/SettingsLib/res/values/config.xml
@@ -31,4 +31,14 @@
<!-- Control whether status bar should distinguish HSPA data icon form UMTS
data icon on devices -->
<bool name="config_hspa_data_distinguishable">false</bool>
+
+ <!-- Edit User avatar explicit package name -->
+ <string name="config_avatar_picker_package" translatable="false">
+ com.android.avatarpicker
+ </string>
+
+ <!-- Edit User avatar explicit activity class -->
+ <string name="config_avatar_picker_class" translatable="false">
+ com.android.avatarpicker.ui.AvatarPickerActivity
+ </string>
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
index cdc3f123eff7..f38e91ac0d8a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -32,6 +32,7 @@ import androidx.annotation.Nullable;
import com.android.internal.util.UserIcons;
import com.android.settingslib.drawable.CircleFramedDrawable;
+import com.android.settingslib.R;
import com.android.settingslib.utils.ThreadUtils;
import com.google.common.util.concurrent.FutureCallback;
@@ -132,6 +133,13 @@ public class EditUserPhotoController {
intent.addCategory(Intent.CATEGORY_DEFAULT);
if (Flags.avatarSync()) {
intent.putExtra(EXTRA_IS_USER_NEW, isUserNew);
+ // Fix vulnerability b/341688848 by explicitly set the class name of avatar picker.
+ if (Flags.fixAvatarCrossUserLeak()) {
+ final String packageName =
+ mActivity.getString(R.string.config_avatar_picker_package);
+ final String className = mActivity.getString(R.string.config_avatar_picker_class);
+ intent.setClassName(packageName, className);
+ }
} else {
// SettingsLib is used by multiple apps therefore we need to know out of all apps
// using settingsLib which one is the one we return value to.
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
index c60eb61c57eb..fb1f715bc68f 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -41,6 +41,7 @@ android_app {
"SettingsLibDisplayUtils",
"SettingsLibSettingsTheme",
"com_android_a11y_menu_flags_lib",
+ "//frameworks/libs/systemui:view_capture",
],
optimize: {
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
index 448472d1b6e4..3db61a58c7a3 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
@@ -22,6 +22,8 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.View.ACCESSIBILITY_LIVE_REGION_POLITE;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+import static com.android.app.viewcapture.ViewCaptureFactory.getViewCaptureAwareWindowManagerInstance;
+
import static java.lang.Math.max;
import android.animation.Animator;
@@ -53,6 +55,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.UiContext;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
import com.android.systemui.accessibility.accessibilitymenu.Flags;
import com.android.systemui.accessibility.accessibilitymenu.R;
@@ -143,7 +146,9 @@ public class A11yMenuOverlayLayout {
final Display display = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
final Context uiContext = mService.createWindowContext(
display, TYPE_ACCESSIBILITY_OVERLAY, /* options= */null);
- final WindowManager windowManager = uiContext.getSystemService(WindowManager.class);
+ final ViewCaptureAwareWindowManager windowManager =
+ getViewCaptureAwareWindowManagerInstance(uiContext,
+ com.android.systemui.Flags.enableViewCaptureTracing());
mLayout = new A11yMenuFrameLayout(uiContext);
updateLayoutPosition(uiContext);
inflateLayoutAndSetOnTouchListener(mLayout, uiContext);
@@ -158,8 +163,8 @@ public class A11yMenuOverlayLayout {
public void clearLayout() {
if (mLayout != null) {
- WindowManager windowManager =
- mLayout.getContext().getSystemService(WindowManager.class);
+ ViewCaptureAwareWindowManager windowManager = getViewCaptureAwareWindowManagerInstance(
+ mLayout.getContext(), com.android.systemui.Flags.enableViewCaptureTracing());
if (windowManager != null) {
windowManager.removeView(mLayout);
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index c5bb33c414b1..7fb88e8d1fcc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -27,8 +27,8 @@ import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.bouncer.ui.BouncerDialogFactory
-import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneActionsViewModel
import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerUserActionsViewModel
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.ExclusiveActivatable
@@ -54,18 +54,17 @@ object Bouncer {
class BouncerScene
@Inject
constructor(
- private val actionsViewModelFactory: BouncerSceneActionsViewModel.Factory,
+ private val actionsViewModelFactory: BouncerUserActionsViewModel.Factory,
private val contentViewModelFactory: BouncerSceneContentViewModel.Factory,
private val dialogFactory: BouncerDialogFactory,
) : ExclusiveActivatable(), Scene {
override val key = Scenes.Bouncer
- private val actionsViewModel: BouncerSceneActionsViewModel by lazy {
+ private val actionsViewModel: BouncerUserActionsViewModel by lazy {
actionsViewModelFactory.create()
}
- override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
- actionsViewModel.actions
+ override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
override suspend fun onActivated(): Nothing {
actionsViewModel.activate()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
index f658169a24ff..8b6de6ab22f3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
@@ -49,10 +49,10 @@ constructor(
) : ExclusiveActivatable(), Scene {
override val key = Scenes.Communal
- override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
- MutableStateFlow<Map<UserAction, UserActionResult>>(
+ override val userActions: Flow<Map<UserAction, UserActionResult>> =
+ MutableStateFlow(
mapOf(
- Swipe(SwipeDirection.End) to UserActionResult(Scenes.Lockscreen),
+ Swipe(SwipeDirection.End) to Scenes.Lockscreen,
)
)
.asStateFlow()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 5f600d3002cc..c7c29f9fdb7c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -24,7 +24,7 @@ import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateContentFloatAsState
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneActionsViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenUserActionsViewModel
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.scene.shared.model.Scenes
@@ -38,17 +38,16 @@ import kotlinx.coroutines.flow.Flow
class LockscreenScene
@Inject
constructor(
- actionsViewModelFactory: LockscreenSceneActionsViewModel.Factory,
+ actionsViewModelFactory: LockscreenUserActionsViewModel.Factory,
private val lockscreenContent: Lazy<LockscreenContent>,
) : ExclusiveActivatable(), Scene {
override val key = Scenes.Lockscreen
- private val actionsViewModel: LockscreenSceneActionsViewModel by lazy {
+ private val actionsViewModel: LockscreenUserActionsViewModel by lazy {
actionsViewModelFactory.create()
}
- override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
- actionsViewModel.actions
+ override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
override suspend fun onActivated(): Nothing {
actionsViewModel.activate()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
index e4c611ee0eb2..a22beccf3448 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
@@ -23,6 +23,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.rememberViewModel
@@ -39,6 +41,7 @@ import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.phone.ui.TintedIconManager
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
@SysUISingleton
class NotificationsShadeOverlay
@@ -59,6 +62,8 @@ constructor(
actionsViewModelFactory.create()
}
+ override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
+
override suspend fun activate(): Nothing {
actionsViewModel.activate()
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
index ea3f066960c1..1f4cd0473086 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
@@ -29,7 +29,7 @@ import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.rememberViewModel
-import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneActionsViewModel
+import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeUserActionsViewModel
import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.Scene
@@ -49,7 +49,7 @@ import kotlinx.coroutines.flow.Flow
class NotificationsShadeScene
@Inject
constructor(
- private val actionsViewModelFactory: NotificationsShadeSceneActionsViewModel.Factory,
+ private val actionsViewModelFactory: NotificationsShadeUserActionsViewModel.Factory,
private val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
private val tintedIconManagerFactory: TintedIconManager.Factory,
@@ -61,12 +61,11 @@ constructor(
override val key = Scenes.NotificationsShade
- private val actionsViewModel: NotificationsShadeSceneActionsViewModel by lazy {
+ private val actionsViewModel: NotificationsShadeUserActionsViewModel by lazy {
actionsViewModelFactory.create()
}
- override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
- actionsViewModel.actions
+ override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
override suspend fun onActivated(): Nothing {
actionsViewModel.activate()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 373383f0a2fe..d34295ea1d22 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -93,8 +93,8 @@ import com.android.systemui.notifications.ui.composable.NotificationStackCutoffG
import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaLandscapeTopOffset
import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaOffset.InQS
-import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneActionsViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneContentViewModel
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsUserActionsViewModel
import com.android.systemui.res.R
import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.shared.model.Scenes
@@ -125,7 +125,7 @@ constructor(
private val shadeSession: SaveableSession,
private val notificationStackScrollView: Lazy<NotificationScrollView>,
private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
- private val actionsViewModelFactory: QuickSettingsSceneActionsViewModel.Factory,
+ private val actionsViewModelFactory: QuickSettingsUserActionsViewModel.Factory,
private val contentViewModelFactory: QuickSettingsSceneContentViewModel.Factory,
private val tintedIconManagerFactory: TintedIconManager.Factory,
private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
@@ -135,12 +135,11 @@ constructor(
) : ExclusiveActivatable(), Scene {
override val key = Scenes.QuickSettings
- private val actionsViewModel: QuickSettingsSceneActionsViewModel by lazy {
+ private val actionsViewModel: QuickSettingsUserActionsViewModel by lazy {
actionsViewModelFactory.create()
}
- override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
- actionsViewModel.actions
+ override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
override suspend fun onActivated(): Nothing {
actionsViewModel.activate()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
index 988c712b7980..f8d0588c9ae6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -34,6 +34,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
import com.android.systemui.compose.modifiers.sysuiResTag
@@ -51,6 +53,7 @@ import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.phone.ui.TintedIconManager
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
@SysUISingleton
class QuickSettingsShadeOverlay
@@ -69,6 +72,8 @@ constructor(
actionsViewModelFactory.create()
}
+ override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
+
override suspend fun activate(): Nothing {
actionsViewModel.activate()
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
index 9316eb90a7a2..e27c7e29ba50 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
@@ -27,8 +27,8 @@ import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.rememberViewModel
-import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneActionsViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneContentViewModel
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeUserActionsViewModel
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.Scene
import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
@@ -43,7 +43,7 @@ import kotlinx.coroutines.flow.Flow
class QuickSettingsShadeScene
@Inject
constructor(
- private val actionsViewModelFactory: QuickSettingsShadeSceneActionsViewModel.Factory,
+ private val actionsViewModelFactory: QuickSettingsShadeUserActionsViewModel.Factory,
private val contentViewModelFactory: QuickSettingsShadeSceneContentViewModel.Factory,
private val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
private val tintedIconManagerFactory: TintedIconManager.Factory,
@@ -53,12 +53,11 @@ constructor(
override val key = Scenes.QuickSettingsShade
- private val actionsViewModel: QuickSettingsShadeSceneActionsViewModel by lazy {
+ private val actionsViewModel: QuickSettingsShadeUserActionsViewModel by lazy {
actionsViewModelFactory.create()
}
- override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
- actionsViewModel.actions
+ override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
override suspend fun onActivated(): Nothing {
actionsViewModel.activate()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ActionableContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ActionableContent.kt
new file mode 100644
index 000000000000..8fe6893cb352
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ActionableContent.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 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.scene.ui.composable
+
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import kotlinx.coroutines.flow.Flow
+
+/** Defines interface for content that can respond to user-actions. */
+interface ActionableContent {
+ /**
+ * The mapping between [UserAction] and destination [UserActionResult]s.
+ *
+ * When the scene framework detects a user action, if the current scene has a map entry for that
+ * user action, the framework starts a transition to the content specified in the map.
+ *
+ * Once the content is shown, the scene framework will read this property and set up a collector
+ * to watch for new mapping values. For each map entry, the scene framework will set up user
+ * input handling for its [UserAction] and, if such a user action is detected, initiate a
+ * transition to the specified [UserActionResult].
+ *
+ * Note that reading from this method does _not_ mean that any user action has occurred.
+ * Instead, the property is read before any user action/gesture is detected so that the
+ * framework can decide whether to set up gesture/input detectors/listeners in case user actions
+ * of the given types ever occur.
+ *
+ * A missing value for a specific [UserAction] means that the user action of the given type is
+ * not currently active in the top-most content (in z-index order) and should be ignored by the
+ * framework until the top-most content changes.
+ */
+ val userActions: Flow<Map<UserAction, UserActionResult>>
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 6fb4724426fc..ae5dd8abb82e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -33,7 +33,7 @@ import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaLandscapeTopOffset
import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaOffset.Default
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.ui.viewmodel.GoneSceneActionsViewModel
+import com.android.systemui.scene.ui.viewmodel.GoneUserActionsViewModel
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import dagger.Lazy
@@ -50,14 +50,13 @@ class GoneScene
constructor(
private val notificationStackScrolLView: Lazy<NotificationScrollView>,
private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
- private val viewModelFactory: GoneSceneActionsViewModel.Factory,
+ private val viewModelFactory: GoneUserActionsViewModel.Factory,
) : ExclusiveActivatable(), Scene {
override val key = Scenes.Gone
- private val actionsViewModel: GoneSceneActionsViewModel by lazy { viewModelFactory.create() }
+ private val actionsViewModel: GoneUserActionsViewModel by lazy { viewModelFactory.create() }
- override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
- actionsViewModel.actions
+ override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
override suspend fun onActivated(): Nothing {
actionsViewModel.activate()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Overlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Overlay.kt
index d62befd10745..609ce90fd684 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Overlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Overlay.kt
@@ -18,9 +18,14 @@ package com.android.systemui.scene.ui.composable
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.OverlayKey
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.lifecycle.Activatable
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
/**
* Defines interface for classes that can describe an "overlay".
@@ -29,9 +34,17 @@ import com.android.systemui.lifecycle.Activatable
* container takes care of rendering any current overlays and allowing overlays to be shown, hidden,
* or replaced based on a user action.
*/
-interface Overlay : Activatable {
+interface Overlay : Activatable, ActionableContent {
/** Uniquely-identifying key for this overlay. The key must be unique within its container. */
val key: OverlayKey
+ /**
+ * The user actions supported by this overlay.
+ *
+ * @see [ActionableContent.userActions]
+ */
+ override val userActions: Flow<Map<UserAction, UserActionResult>>
+ get() = flowOf(mapOf(Back to UserActionResult.HideOverlay(key)))
+
@Composable fun ContentScope.Content(modifier: Modifier)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Scene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Scene.kt
index 5319ec345d00..8d8ab8ee7949 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Scene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Scene.kt
@@ -20,10 +20,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.UserAction
-import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.lifecycle.Activatable
-import kotlinx.coroutines.flow.Flow
/**
* Defines interface for classes that can describe a "scene".
@@ -33,32 +30,10 @@ import kotlinx.coroutines.flow.Flow
* based on either user action (for example, swiping down while on the lock screen scene may switch
* to the shade scene).
*/
-interface Scene : Activatable {
+interface Scene : Activatable, ActionableContent {
/** Uniquely-identifying key for this scene. The key must be unique within its container. */
val key: SceneKey
- /**
- * The mapping between [UserAction] and destination [UserActionResult]s.
- *
- * When the scene framework detects a user action, if the current scene has a map entry for that
- * user action, the framework starts a transition to the scene in the map.
- *
- * Once the [Scene] becomes the current one, the scene framework will read this property and set
- * up a collector to watch for new mapping values. If every map entry provided by the scene, the
- * framework will set up user input handling for its [UserAction] and, if such a user action is
- * detected, initiate a transition to the specified [UserActionResult].
- *
- * Note that reading from this method does _not_ mean that any user action has occurred.
- * Instead, the property is read before any user action/gesture is detected so that the
- * framework can decide whether to set up gesture/input detectors/listeners in case user actions
- * of the given types ever occur.
- *
- * Note that a missing value for a specific [UserAction] means that the user action of the given
- * type is not currently active in the scene and should be ignored by the framework, while the
- * current scene is this one.
- */
- val destinationScenes: Flow<Map<UserAction, UserActionResult>>
-
@Composable fun SceneScope.Content(modifier: Modifier)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 851fa3ff005e..a7e41ce05f58 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -104,7 +104,7 @@ fun SceneContainer(
// TODO(b/359173565): Add overlay user actions when the API is final.
LaunchedEffect(currentSceneKey) {
try {
- sceneByKey[currentSceneKey]?.destinationScenes?.collectLatest { userActions ->
+ sceneByKey[currentSceneKey]?.userActions?.collectLatest { userActions ->
userActionsByContentKey[currentSceneKey] =
viewModel.resolveSceneFamilies(userActions)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 5fcf5226a585..a03bf43c4c8f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -108,8 +108,8 @@ import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.Scene
import com.android.systemui.shade.shared.model.ShadeMode
-import com.android.systemui.shade.ui.viewmodel.ShadeSceneActionsViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeSceneContentViewModel
+import com.android.systemui.shade.ui.viewmodel.ShadeUserActionsViewModel
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.statusbar.phone.StatusBarLocation
@@ -154,7 +154,7 @@ class ShadeScene
constructor(
private val shadeSession: SaveableSession,
private val notificationStackScrollView: Lazy<NotificationScrollView>,
- private val actionsViewModelFactory: ShadeSceneActionsViewModel.Factory,
+ private val actionsViewModelFactory: ShadeUserActionsViewModel.Factory,
private val contentViewModelFactory: ShadeSceneContentViewModel.Factory,
private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
private val tintedIconManagerFactory: TintedIconManager.Factory,
@@ -167,7 +167,7 @@ constructor(
override val key = Scenes.Shade
- private val actionsViewModel: ShadeSceneActionsViewModel by lazy {
+ private val actionsViewModel: ShadeUserActionsViewModel by lazy {
actionsViewModelFactory.create()
}
@@ -175,8 +175,7 @@ constructor(
actionsViewModel.activate()
}
- override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
- actionsViewModel.actions
+ override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
@Composable
override fun SceneScope.Content(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 4aa50b586c1b..b30f2b7002ce 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -399,26 +399,53 @@ private class AnimatedStateImpl<T, Delta>(
val fromValue = sharedValue[transition.fromContent]
val toValue = sharedValue[transition.toContent]
- return if (fromValue != null && toValue != null) {
- if (fromValue == toValue) {
- // Optimization: avoid reading progress if the values are the same, so we don't
- // relayout/redraw for nothing.
- fromValue
- } else {
- val overscrollSpec = transition.currentOverscrollSpec
- val progress =
- when {
- overscrollSpec == null -> {
- if (canOverflow) transition.progress
- else transition.progress.fastCoerceIn(0f, 1f)
- }
- overscrollSpec.content == transition.toContent -> 1f
- else -> 0f
- }
-
- sharedValue.type.lerp(fromValue, toValue, progress)
+ if (fromValue == null && toValue == null) {
+ return null
+ }
+
+ if (fromValue != null && toValue != null) {
+ return interpolateSharedValue(fromValue, toValue, transition, sharedValue)
+ }
+
+ if (transition is TransitionState.Transition.ReplaceOverlay) {
+ val currentSceneValue = sharedValue[transition.currentScene]
+ if (currentSceneValue != null) {
+ return interpolateSharedValue(
+ fromValue = fromValue ?: currentSceneValue,
+ toValue = toValue ?: currentSceneValue,
+ transition,
+ sharedValue,
+ )
+ }
+ }
+
+ return fromValue ?: toValue
+ }
+
+ private fun interpolateSharedValue(
+ fromValue: T,
+ toValue: T,
+ transition: TransitionState.Transition,
+ sharedValue: SharedValue<T, *>,
+ ): T? {
+ if (fromValue == toValue) {
+ // Optimization: avoid reading progress if the values are the same, so we don't
+ // relayout/redraw for nothing.
+ return fromValue
+ }
+
+ val overscrollSpec = transition.currentOverscrollSpec
+ val progress =
+ when {
+ overscrollSpec == null -> {
+ if (canOverflow) transition.progress
+ else transition.progress.fastCoerceIn(0f, 1f)
+ }
+ overscrollSpec.content == transition.toContent -> 1f
+ else -> 0f
}
- } else fromValue ?: toValue
+
+ return sharedValue.type.lerp(fromValue, toValue, progress)
}
private fun transition(sharedValue: SharedValue<T, Delta>): TransitionState.Transition? {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 9b1740dc700a..4c0feb883c84 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -313,10 +313,27 @@ internal class ElementNode(
// If this element is not supposed to be laid out now, either because it is not part of any
// ongoing transition or the other content of its transition is overscrolling, then lay out
// the element normally and don't place it.
- val overscrollScene = transition?.currentOverscrollSpec?.content
- val isOtherSceneOverscrolling = overscrollScene != null && overscrollScene != content.key
- if (isOtherSceneOverscrolling) {
- return doNotPlace(measurable, constraints)
+ val overscrollContent = transition?.currentOverscrollSpec?.content
+ if (overscrollContent != null && overscrollContent != content.key) {
+ when (transition) {
+ is TransitionState.Transition.ChangeScene ->
+ return doNotPlace(measurable, constraints)
+
+ // If we are overscrolling an overlay that does not contain an element that is in
+ // the current scene, place it in that scene otherwise the element won't be placed
+ // at all.
+ is TransitionState.Transition.ShowOrHideOverlay,
+ is TransitionState.Transition.ReplaceOverlay -> {
+ if (
+ content.key == transition.currentScene &&
+ overscrollContent !in element.stateByContent
+ ) {
+ return placeNormally(measurable, constraints)
+ } else {
+ return doNotPlace(measurable, constraints)
+ }
+ }
+ }
}
val placeable =
@@ -1230,17 +1247,30 @@ private inline fun <T> computeValue(
// elements follow the finger direction.
val isSharedElement = fromState != null && toState != null
if (isSharedElement && isSharedElementEnabled(element.key, transition)) {
- val start = contentValue(fromState!!)
- val end = contentValue(toState!!)
-
- // TODO(b/316901148): Remove checks to isSpecified() once the lookahead pass runs for all
- // nodes before the intermediate layout pass.
- if (!isSpecified(start)) return end
- if (!isSpecified(end)) return start
+ return interpolateSharedElement(
+ transition = transition,
+ contentValue = contentValue,
+ fromState = fromState!!,
+ toState = toState!!,
+ isSpecified = isSpecified,
+ lerp = lerp,
+ )
+ }
- // Make sure we don't read progress if values are the same and we don't need to interpolate,
- // so we don't invalidate the phase where this is read.
- return if (start == end) start else lerp(start, end, transition.progress)
+ // If we are replacing an overlay and the element is both in a single overlay and in the current
+ // scene, interpolate the state of the element using the current scene as the other scene.
+ if (!isSharedElement && transition is TransitionState.Transition.ReplaceOverlay) {
+ val currentSceneState = element.stateByContent[transition.currentScene]
+ if (currentSceneState != null) {
+ return interpolateSharedElement(
+ transition = transition,
+ contentValue = contentValue,
+ fromState = fromState ?: currentSceneState,
+ toState = toState ?: currentSceneState,
+ isSpecified = isSpecified,
+ lerp = lerp,
+ )
+ }
}
// Get the transformed value, i.e. the target value at the beginning (for entering elements) or
@@ -1383,3 +1413,24 @@ private inline fun <T> computeValue(
lerp(idleValue, targetValue, rangeProgress)
}
}
+
+private inline fun <T> interpolateSharedElement(
+ transition: TransitionState.Transition,
+ contentValue: (Element.State) -> T,
+ fromState: Element.State,
+ toState: Element.State,
+ isSpecified: (T) -> Boolean,
+ lerp: (T, T, Float) -> T
+): T {
+ val start = contentValue(fromState)
+ val end = contentValue(toState)
+
+ // TODO(b/316901148): Remove checks to isSpecified() once the lookahead pass runs for all
+ // nodes before the intermediate layout pass.
+ if (!isSpecified(start)) return end
+ if (!isSpecified(end)) return start
+
+ // Make sure we don't read progress if values are the same and we don't need to interpolate,
+ // so we don't invalidate the phase where this is read.
+ return if (start == end) start else lerp(start, end, transition.progress)
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
index bec2bb2baa3c..c25478b35790 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
@@ -22,10 +22,12 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
@@ -42,6 +44,8 @@ import com.android.compose.animation.scene.TestOverlays.OverlayA
import com.android.compose.animation.scene.TestOverlays.OverlayB
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.test.assertSizeIsEqualTo
+import com.android.compose.test.subjects.assertThat
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import org.junit.Rule
import org.junit.Test
@@ -524,4 +528,124 @@ class OverlayTest {
}
}
}
+
+ @Test
+ fun replaceAnimation_elementInCurrentSceneAndOneOverlay() {
+ val sharedIntKey = ValueKey("sharedInt")
+ val sharedIntValueByContent = mutableMapOf<ContentKey, Int>()
+
+ @Composable
+ fun SceneScope.animateContentInt(targetValue: Int) {
+ val animatedValue = animateContentIntAsState(targetValue, sharedIntKey)
+ LaunchedEffect(animatedValue) {
+ try {
+ snapshotFlow { animatedValue.value }
+ .collect { sharedIntValueByContent[contentKey] = it }
+ } finally {
+ sharedIntValueByContent.remove(contentKey)
+ }
+ }
+ }
+
+ rule.testReplaceOverlayTransition(
+ currentSceneContent = {
+ Box(Modifier.size(width = 180.dp, height = 120.dp)) {
+ animateContentInt(targetValue = 1_000)
+ Foo(width = 60.dp, height = 40.dp)
+ }
+ },
+ fromContent = {},
+ fromAlignment = Alignment.TopStart,
+ toContent = {
+ animateContentInt(targetValue = 2_000)
+ Foo(width = 100.dp, height = 80.dp)
+ },
+ transition = {
+ // 4 frames of animation
+ spec = tween(4 * 16, easing = LinearEasing)
+ },
+ ) {
+ // Foo moves from (0,0) with a size of 60x40dp to centered (in a 180x120dp Box) with a
+ // size of 100x80dp, so at (40,20).
+ //
+ // The animated Int goes from 1_000 to 2_000.
+ before {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertSizeIsEqualTo(60.dp, 40.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+ rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+ rule.onNode(isElement(TestElements.Foo, content = OverlayB)).assertDoesNotExist()
+
+ assertThat(sharedIntValueByContent).containsEntry(SceneA, 1_000)
+ assertThat(sharedIntValueByContent).doesNotContainKey(OverlayA)
+ assertThat(sharedIntValueByContent).doesNotContainKey(OverlayB)
+ }
+
+ at(16) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayB))
+ .assertSizeIsEqualTo(70.dp, 50.dp)
+ .assertPositionInRootIsEqualTo(10.dp, 5.dp)
+
+ assertThat(sharedIntValueByContent).containsEntry(SceneA, 1_250)
+ assertThat(sharedIntValueByContent).doesNotContainKey(OverlayA)
+ assertThat(sharedIntValueByContent).containsEntry(OverlayB, 1_250)
+ }
+
+ at(32) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayB))
+ .assertSizeIsEqualTo(80.dp, 60.dp)
+ .assertPositionInRootIsEqualTo(20.dp, 10.dp)
+
+ assertThat(sharedIntValueByContent).containsEntry(SceneA, 1_500)
+ assertThat(sharedIntValueByContent).doesNotContainKey(OverlayA)
+ assertThat(sharedIntValueByContent).containsEntry(OverlayB, 1_500)
+ }
+
+ at(48) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayB))
+ .assertSizeIsEqualTo(90.dp, 70.dp)
+ .assertPositionInRootIsEqualTo(30.dp, 15.dp)
+
+ assertThat(sharedIntValueByContent).containsEntry(SceneA, 1_750)
+ assertThat(sharedIntValueByContent).doesNotContainKey(OverlayA)
+ assertThat(sharedIntValueByContent).containsEntry(OverlayB, 1_750)
+ }
+
+ after {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayB))
+ .assertSizeIsEqualTo(100.dp, 80.dp)
+ .assertPositionInRootIsEqualTo(40.dp, 20.dp)
+
+ // Outside of transitions, the value is equal to the target value in each content.
+ assertThat(sharedIntValueByContent).containsEntry(SceneA, 1_000)
+ assertThat(sharedIntValueByContent).doesNotContainKey(OverlayA)
+ assertThat(sharedIntValueByContent).containsEntry(OverlayB, 2_000)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/docs/scene.md b/packages/SystemUI/docs/scene.md
index a7740c677d51..0ac15c583b29 100644
--- a/packages/SystemUI/docs/scene.md
+++ b/packages/SystemUI/docs/scene.md
@@ -124,8 +124,8 @@ Each scene is defined as an implementation of the
[`Scene`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Scene.kt)
interface, which has three parts: 1. The `key` property returns the
[`SceneKey`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt)
-that uniquely identifies that scene 2. The `destinationScenes` `Flow` returns
-the (potentially ever-changing) set of navigation edges to other scenes, based
+that uniquely identifies that scene 2. The `userActions` `Flow` returns
+the (potentially ever-changing) set of navigation edges to other content, based
on user-actions, which is how the navigation graph is defined (see
[the Scene navigation](#Scene-navigation) section for more) 3. The `Content`
function which uses
@@ -141,7 +141,7 @@ For example:
@SysUISingleton class YourScene @Inject constructor( /* your dependencies here */ ) : Scene {
override val key = SceneKey.YourScene
- override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
+ override val userActions: StateFlow<Map<UserAction, SceneModel>> =
MutableStateFlow<Map<UserAction, SceneModel>>(
mapOf(
// This is where scene navigation is defined, more on that below.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModelTest.kt
index a86a0c022c21..f58bbc3cf0cf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModelTest.kt
@@ -44,17 +44,17 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableSceneContainer
-class BouncerSceneActionsViewModelTest : SysuiTestCase() {
+class BouncerUserActionsViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private lateinit var underTest: BouncerSceneActionsViewModel
+ private lateinit var underTest: BouncerUserActionsViewModel
@Before
fun setUp() {
kosmos.sceneContainerStartable.start()
- underTest = kosmos.bouncerSceneActionsViewModel
+ underTest = kosmos.bouncerUserActionsViewModel
underTest.activateIn(testScope)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
index ca15eff4610b..34d926a23edb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
@@ -24,6 +24,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.contextualeducation.GestureType.ALL_APPS
import com.android.systemui.contextualeducation.GestureType.BACK
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.education.data.model.GestureEduModel
@@ -220,17 +221,19 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
verify(kosmos.mockEduInputManager)
.registerKeyGestureEventListener(any(), listenerCaptor.capture())
- val backGestureEvent =
+ val allAppsKeyGestureEvent =
KeyGestureEvent(
/* deviceId= */ 1,
- intArrayOf(KeyEvent.KEYCODE_ESCAPE),
+ IntArray(0),
KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_BACK
+ KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS
)
- listenerCaptor.value.onKeyGestureEvent(backGestureEvent)
+ listenerCaptor.value.onKeyGestureEvent(allAppsKeyGestureEvent)
val model by
- collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK))
+ collectLastValue(
+ kosmos.contextualEducationRepository.readGestureEduModelFlow(ALL_APPS)
+ )
assertThat(model?.lastShortcutTriggeredTime).isEqualTo(eduClock.instant())
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
index cd0c58feebed..8b5f59457e6e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
@@ -19,16 +19,23 @@ package com.android.systemui.education.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.contextualeducation.GestureType.ALL_APPS
import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.education.data.repository.contextualEducationRepository
import com.android.systemui.education.data.repository.fakeEduClock
+import com.android.systemui.inputdevice.data.model.UserDeviceConnectionStatus
+import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -36,24 +43,190 @@ class KeyboardTouchpadStatsInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val underTest = kosmos.keyboardTouchpadEduStatsInteractor
+ private val repository = kosmos.contextualEducationRepository
+ private val fakeClock = kosmos.fakeEduClock
+ private val initialDelayElapsedDuration =
+ KeyboardTouchpadEduStatsInteractorImpl.initialDelayDuration + 1.seconds
+
+ @Test
+ fun dataUpdatedOnIncrementSignalCountWhenTouchpadConnected() =
+ testScope.runTest {
+ setUpForInitialDelayElapse()
+ whenever(mockUserInputDeviceRepository.isAnyTouchpadConnectedForUser)
+ .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = true, userId = 0)))
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(BACK)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue + 1)
+ }
+
+ @Test
+ fun dataUnchangedOnIncrementSignalCountWhenTouchpadDisconnected() =
+ testScope.runTest {
+ setUpForInitialDelayElapse()
+ whenever(mockUserInputDeviceRepository.isAnyTouchpadConnectedForUser)
+ .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = false, userId = 0)))
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(BACK)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue)
+ }
+
+ @Test
+ fun dataUpdatedOnIncrementSignalCountWhenKeyboardConnected() =
+ testScope.runTest {
+ setUpForInitialDelayElapse()
+ whenever(mockUserInputDeviceRepository.isAnyKeyboardConnectedForUser)
+ .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = true, userId = 0)))
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(ALL_APPS))
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(ALL_APPS)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue + 1)
+ }
+
+ @Test
+ fun dataUnchangedOnIncrementSignalCountWhenKeyboardDisconnected() =
+ testScope.runTest {
+ setUpForInitialDelayElapse()
+ whenever(mockUserInputDeviceRepository.isAnyKeyboardConnectedForUser)
+ .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = false, userId = 0)))
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(ALL_APPS))
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(ALL_APPS)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue)
+ }
+
+ @Test
+ fun dataUpdatedOnIncrementSignalCountAfterOobeLaunchInitialDelay() =
+ testScope.runTest {
+ setUpForDeviceConnection()
+ whenever(mockTutorialSchedulerRepository.launchTime(any<DeviceType>()))
+ .thenReturn(fakeClock.instant())
+ fakeClock.offset(initialDelayElapsedDuration)
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(BACK)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue + 1)
+ }
@Test
- fun dataUpdatedOnIncrementSignalCount() =
+ fun dataUnchangedOnIncrementSignalCountBeforeOobeLaunchInitialDelay() =
testScope.runTest {
- val model by
- collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK))
+ setUpForDeviceConnection()
+ whenever(mockTutorialSchedulerRepository.launchTime(any<DeviceType>()))
+ .thenReturn(fakeClock.instant())
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
val originalValue = model!!.signalCount
underTest.incrementSignalCount(BACK)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue)
+ }
+
+ @Test
+ fun dataUpdatedOnIncrementSignalCountAfterTouchpadConnectionInitialDelay() =
+ testScope.runTest {
+ setUpForDeviceConnection()
+ repository.updateEduDeviceConnectionTime { model ->
+ model.copy(touchpadFirstConnectionTime = fakeClock.instant())
+ }
+ fakeClock.offset(initialDelayElapsedDuration)
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(BACK)
+
assertThat(model?.signalCount).isEqualTo(originalValue + 1)
}
@Test
+ fun dataUnchangedOnIncrementSignalCountBeforeTouchpadConnectionInitialDelay() =
+ testScope.runTest {
+ setUpForDeviceConnection()
+ repository.updateEduDeviceConnectionTime { model ->
+ model.copy(touchpadFirstConnectionTime = fakeClock.instant())
+ }
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(BACK)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue)
+ }
+
+ @Test
+ fun dataUpdatedOnIncrementSignalCountAfterKeyboardConnectionInitialDelay() =
+ testScope.runTest {
+ setUpForDeviceConnection()
+ repository.updateEduDeviceConnectionTime { model ->
+ model.copy(keyboardFirstConnectionTime = fakeClock.instant())
+ }
+ fakeClock.offset(initialDelayElapsedDuration)
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(ALL_APPS))
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(ALL_APPS)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue + 1)
+ }
+
+ @Test
+ fun dataUnchangedOnIncrementSignalCountBeforeKeyboardConnectionInitialDelay() =
+ testScope.runTest {
+ setUpForDeviceConnection()
+ repository.updateEduDeviceConnectionTime { model ->
+ model.copy(keyboardFirstConnectionTime = fakeClock.instant())
+ }
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(ALL_APPS))
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(ALL_APPS)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue)
+ }
+
+ @Test
+ fun dataUnchangedOnIncrementSignalCountWhenNoSetupTime() =
+ testScope.runTest {
+ whenever(mockUserInputDeviceRepository.isAnyTouchpadConnectedForUser)
+ .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = true, userId = 0)))
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(BACK)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue)
+ }
+
+ @Test
fun dataAddedOnUpdateShortcutTriggerTime() =
testScope.runTest {
- val model by
- collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK))
+ val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
assertThat(model?.lastShortcutTriggeredTime).isNull()
underTest.updateShortcutTriggerTime(BACK)
assertThat(model?.lastShortcutTriggeredTime).isEqualTo(kosmos.fakeEduClock.instant())
}
+
+ private suspend fun setUpForInitialDelayElapse() {
+ whenever(mockTutorialSchedulerRepository.launchTime(any<DeviceType>()))
+ .thenReturn(fakeClock.instant())
+ fakeClock.offset(initialDelayElapsedDuration)
+ }
+
+ private fun setUpForDeviceConnection() {
+ whenever(mockUserInputDeviceRepository.isAnyTouchpadConnectedForUser)
+ .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = true, userId = 0)))
+ whenever(mockUserInputDeviceRepository.isAnyKeyboardConnectedForUser)
+ .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = true, userId = 0)))
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
index c66ebf3a31e0..4253c29d241c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
@@ -63,7 +63,7 @@ import platform.test.runner.parameterized.Parameters
@RunWith(ParameterizedAndroidJunit4::class)
@RunWithLooper
@EnableSceneContainer
-class LockscreenSceneActionsViewModelTest : SysuiTestCase() {
+class LockscreenUserActionsViewModelTest : SysuiTestCase() {
companion object {
private const val parameterCount = 6
@@ -170,7 +170,7 @@ class LockscreenSceneActionsViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
- fun destinationScenes() =
+ fun userActions() =
testScope.runTest {
underTest.activateIn(this)
kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
@@ -193,9 +193,9 @@ class LockscreenSceneActionsViewModelTest : SysuiTestCase() {
},
)
- val destinationScenes by collectLastValue(underTest.actions)
+ val userActions by collectLastValue(underTest.actions)
val downDestination =
- destinationScenes?.get(
+ userActions?.get(
Swipe(
SwipeDirection.Down,
fromSource = Edge.Top.takeIf { downFromEdge },
@@ -227,11 +227,10 @@ class LockscreenSceneActionsViewModelTest : SysuiTestCase() {
val upScene by
collectLastValue(
- (destinationScenes?.get(Swipe(SwipeDirection.Up))
- as? UserActionResult.ChangeScene)
- ?.toScene
- ?.let { scene -> kosmos.sceneInteractor.resolveSceneFamily(scene) }
- ?: flowOf(null)
+ (userActions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene?.let {
+ scene ->
+ kosmos.sceneInteractor.resolveSceneFamily(scene)
+ } ?: flowOf(null)
)
assertThat(upScene)
@@ -244,11 +243,10 @@ class LockscreenSceneActionsViewModelTest : SysuiTestCase() {
val leftScene by
collectLastValue(
- (destinationScenes?.get(Swipe(SwipeDirection.Left))
- as? UserActionResult.ChangeScene)
- ?.toScene
- ?.let { scene -> kosmos.sceneInteractor.resolveSceneFamily(scene) }
- ?: flowOf(null)
+ (userActions?.get(Swipe.Left) as? UserActionResult.ChangeScene)?.toScene?.let {
+ scene ->
+ kosmos.sceneInteractor.resolveSceneFamily(scene)
+ } ?: flowOf(null)
)
assertThat(leftScene)
@@ -260,8 +258,8 @@ class LockscreenSceneActionsViewModelTest : SysuiTestCase() {
)
}
- private fun createLockscreenSceneViewModel(): LockscreenSceneActionsViewModel {
- return LockscreenSceneActionsViewModel(
+ private fun createLockscreenSceneViewModel(): LockscreenUserActionsViewModel {
+ return LockscreenUserActionsViewModel(
deviceEntryInteractor = kosmos.deviceEntryInteractor,
communalInteractor = kosmos.communalInteractor,
shadeInteractor = kosmos.shadeInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModelTest.kt
index ed7f96fb2b66..46b02e92a4f9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModelTest.kt
@@ -37,7 +37,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.ui.viewmodel.notificationsShadeSceneActionsViewModel
+import com.android.systemui.shade.ui.viewmodel.notificationsShadeUserActionsViewModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -52,14 +52,14 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@EnableSceneContainer
-class NotificationsShadeSceneActionsViewModelTest : SysuiTestCase() {
+class NotificationsShadeUserActionsViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
- private val underTest by lazy { kosmos.notificationsShadeSceneActionsViewModel }
+ private val underTest by lazy { kosmos.notificationsShadeUserActionsViewModel }
@Test
fun upTransitionSceneKey_deviceLocked_lockscreen() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelTest.kt
index ba527d7ad2b8..32772d20a76a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelTest.kt
@@ -53,14 +53,14 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@EnableSceneContainer
-class QuickSettingsShadeSceneActionsViewModelTest : SysuiTestCase() {
+class QuickSettingsShadeUserActionsViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val sceneInteractor = kosmos.sceneInteractor
private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
- private val underTest by lazy { kosmos.quickSettingsShadeSceneActionsViewModel }
+ private val underTest by lazy { kosmos.quickSettingsShadeUserActionsViewModel }
@Test
fun upTransitionSceneKey_deviceLocked_lockscreen() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt
index f26a9db56450..6986cf8ee7dc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt
@@ -57,7 +57,7 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@EnableSceneContainer
-class QuickSettingsSceneActionsViewModelTest : SysuiTestCase() {
+class QuickSettingsUserActionsViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -67,7 +67,7 @@ class QuickSettingsSceneActionsViewModelTest : SysuiTestCase() {
private val sceneBackInteractor = kosmos.sceneBackInteractor
private val sceneContainerStartable = kosmos.sceneContainerStartable
- private lateinit var underTest: QuickSettingsSceneActionsViewModel
+ private lateinit var underTest: QuickSettingsUserActionsViewModel
@Before
fun setUp() {
@@ -75,7 +75,7 @@ class QuickSettingsSceneActionsViewModelTest : SysuiTestCase() {
sceneContainerStartable.start()
underTest =
- QuickSettingsSceneActionsViewModel(
+ QuickSettingsUserActionsViewModel(
qsSceneAdapter = qsFlexiglassAdapter,
sceneBackInteractor = sceneBackInteractor,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index f365afbfcc06..4f7c01358a7f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -49,7 +49,7 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneActionsViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenUserActionsViewModel
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -65,10 +65,10 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.ui.viewmodel.ShadeSceneActionsViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeSceneContentViewModel
-import com.android.systemui.shade.ui.viewmodel.shadeSceneActionsViewModel
+import com.android.systemui.shade.ui.viewmodel.ShadeUserActionsViewModel
import com.android.systemui.shade.ui.viewmodel.shadeSceneContentViewModel
+import com.android.systemui.shade.ui.viewmodel.shadeUserActionsViewModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
@@ -145,8 +145,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
private lateinit var bouncerActionButtonInteractor: BouncerActionButtonInteractor
private lateinit var bouncerSceneContentViewModel: BouncerSceneContentViewModel
- private val lockscreenSceneActionsViewModel by lazy {
- LockscreenSceneActionsViewModel(
+ private val mLockscreenUserActionsViewModel by lazy {
+ LockscreenUserActionsViewModel(
deviceEntryInteractor = deviceEntryInteractor,
communalInteractor = communalInteractor,
shadeInteractor = kosmos.shadeInteractor,
@@ -154,7 +154,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
}
private lateinit var shadeSceneContentViewModel: ShadeSceneContentViewModel
- private lateinit var shadeSceneActionsViewModel: ShadeSceneActionsViewModel
+ private lateinit var mShadeUserActionsViewModel: ShadeUserActionsViewModel
private val powerInteractor by lazy { kosmos.powerInteractor }
@@ -191,14 +191,14 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
bouncerSceneContentViewModel = kosmos.bouncerSceneContentViewModel
shadeSceneContentViewModel = kosmos.shadeSceneContentViewModel
- shadeSceneActionsViewModel = kosmos.shadeSceneActionsViewModel
+ mShadeUserActionsViewModel = kosmos.shadeUserActionsViewModel
val startable = kosmos.sceneContainerStartable
startable.start()
- lockscreenSceneActionsViewModel.activateIn(testScope)
+ mLockscreenUserActionsViewModel.activateIn(testScope)
shadeSceneContentViewModel.activateIn(testScope)
- shadeSceneActionsViewModel.activateIn(testScope)
+ mShadeUserActionsViewModel.activateIn(testScope)
bouncerSceneContentViewModel.activateIn(testScope)
sceneContainerViewModel.activateIn(testScope)
@@ -229,7 +229,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
@Test
fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
testScope.runTest {
- val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
+ val actions by collectLastValue(mLockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
@@ -250,7 +250,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
- val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
+ val actions by collectLastValue(mLockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
@@ -262,7 +262,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
@Test
fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
testScope.runTest {
- val actions by collectLastValue(shadeSceneActionsViewModel.actions)
+ val actions by collectLastValue(mShadeUserActionsViewModel.actions)
val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
assertCurrentScene(Scenes.Lockscreen)
@@ -283,7 +283,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
@Test
fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() =
testScope.runTest {
- val actions by collectLastValue(shadeSceneActionsViewModel.actions)
+ val actions by collectLastValue(mShadeUserActionsViewModel.actions)
val canSwipeToEnter by collectLastValue(deviceEntryInteractor.canSwipeToEnter)
val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
@@ -369,7 +369,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
testScope.runTest {
unlockDevice()
- val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
+ val actions by collectLastValue(mLockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
@@ -392,7 +392,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() =
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
- val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
+ val actions by collectLastValue(mLockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
@@ -411,7 +411,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
fun bouncerActionButtonClick_opensEmergencyServicesDialer() =
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
- val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
+ val actions by collectLastValue(mLockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
@@ -432,7 +432,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
startPhoneCall()
- val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
+ val actions by collectLastValue(mLockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
index b52627570246..03106eca1f63 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
@@ -43,17 +43,17 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@EnableSceneContainer
-class GoneSceneActionsViewModelTest : SysuiTestCase() {
+class GoneUserActionsViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val shadeRepository by lazy { kosmos.shadeRepository }
- private lateinit var underTest: GoneSceneActionsViewModel
+ private lateinit var underTest: GoneUserActionsViewModel
@Before
fun setUp() {
underTest =
- GoneSceneActionsViewModel(
+ GoneUserActionsViewModel(
shadeInteractor = kosmos.shadeInteractor,
)
underTest.activateIn(testScope)
@@ -62,21 +62,21 @@ class GoneSceneActionsViewModelTest : SysuiTestCase() {
@Test
fun downTransitionKey_splitShadeEnabled_isGoneToSplitShade() =
testScope.runTest {
- val destinationScenes by collectLastValue(underTest.actions)
+ val userActions by collectLastValue(underTest.actions)
shadeRepository.setShadeLayoutWide(true)
runCurrent()
- assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.transitionKey)
+ assertThat(userActions?.get(Swipe(SwipeDirection.Down))?.transitionKey)
.isEqualTo(ToSplitShade)
}
@Test
fun downTransitionKey_splitShadeDisabled_isNull() =
testScope.runTest {
- val destinationScenes by collectLastValue(underTest.actions)
+ val userActions by collectLastValue(underTest.actions)
shadeRepository.setShadeLayoutWide(false)
runCurrent()
- assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.transitionKey).isNull()
+ assertThat(userActions?.get(Swipe(SwipeDirection.Down))?.transitionKey).isNull()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/UserActionsViewModelTest.kt
index 900f2a464588..972afb58352d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/UserActionsViewModelTest.kt
@@ -42,12 +42,12 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
-class SceneActionsViewModelTest : SysuiTestCase() {
+class UserActionsViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val underTest = FakeSceneActionsViewModel()
+ private val underTest = FakeUserActionsViewModel()
@Test
fun actions_emptyBeforeActivation() =
@@ -115,7 +115,7 @@ class SceneActionsViewModelTest : SysuiTestCase() {
assertThat(actions).isEmpty()
}
- private class FakeSceneActionsViewModel : SceneActionsViewModel() {
+ private class FakeUserActionsViewModel : UserActionsViewModel() {
val upstream = MutableStateFlow<Map<UserAction, UserActionResult>>(emptyMap())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
index a931e656c3c6..9f3e126ed1e8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
@@ -64,7 +64,7 @@ import org.junit.runner.RunWith
@TestableLooper.RunWithLooper
@EnableSceneContainer
@DisableFlags(DualShade.FLAG_NAME)
-class ShadeSceneActionsViewModelTest : SysuiTestCase() {
+class ShadeUserActionsViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -72,7 +72,7 @@ class ShadeSceneActionsViewModelTest : SysuiTestCase() {
private val shadeRepository by lazy { kosmos.shadeRepository }
private val qsSceneAdapter by lazy { kosmos.fakeQSSceneAdapter }
- private val underTest: ShadeSceneActionsViewModel by lazy { kosmos.shadeSceneActionsViewModel }
+ private val underTest: ShadeUserActionsViewModel by lazy { kosmos.shadeUserActionsViewModel }
@Before
fun setUp() {
diff --git a/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml
index e06bfdc500da..368fe829cf9f 100644
--- a/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml
+++ b/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml
@@ -52,7 +52,7 @@
<Button
android:id="@android:id/button1"
style="?android:attr/buttonBarPositiveButtonStyle"
- android:layout_marginStart="8dp"
+ android:layout_marginStart="@dimen/dialog_button_side_margin"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</com.android.internal.widget.ButtonBarLayout>
diff --git a/packages/SystemUI/res/layout/screen_share_dialog.xml b/packages/SystemUI/res/layout/screen_share_dialog.xml
index aa083ad9fdea..0533c7e3fc50 100644
--- a/packages/SystemUI/res/layout/screen_share_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_share_dialog.xml
@@ -64,30 +64,27 @@
android:layout_height="wrap_content"
android:text="@string/screenrecord_permission_dialog_warning_entire_screen"
style="@style/TextAppearance.Dialog.Body.Message"
- android:gravity="start"/>
+ android:gravity="start"
+ android:textAlignment="gravity"/>
<!-- Buttons -->
<com.android.internal.widget.ButtonBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
- android:layout_marginTop="@dimen/screenrecord_buttons_margin_top">
+ android:layout_marginTop="@dimen/screenrecord_buttons_margin_top"
+ android:gravity="end">
<Button
android:id="@android:id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_weight="0"
android:text="@string/cancel"
style="@style/Widget.Dialog.Button.BorderButton" />
- <Space
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"/>
<Button
android:id="@android:id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_weight="0"
+ android:layout_marginStart="@dimen/dialog_button_side_margin"
android:text="@string/screenrecord_continue"
style="@style/Widget.Dialog.Button" />
</com.android.internal.widget.ButtonBarLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e5750d278bfe..141d03599867 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1751,6 +1751,7 @@
<!-- System UI Dialog -->
<dimen name="dialog_title_text_size">24sp</dimen>
+ <dimen name="dialog_button_side_margin">8dp</dimen>
<!-- Internet panel related dimensions -->
<dimen name="internet_dialog_list_max_height">662dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d3d757bcdb46..be74291e705a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3699,6 +3699,22 @@
-->
<string name="shortcut_helper_key_combinations_or_separator">or</string>
+ <!-- Keyboard touchpad tutorial scheduler-->
+ <!-- Notification title for launching keyboard tutorial [CHAR_LIMIT=100] -->
+ <string name="launch_keyboard_tutorial_notification_title">Navigate using your keyboard</string>
+ <!-- Notification text for launching keyboard tutorial [CHAR_LIMIT=100] -->
+ <string name="launch_keyboard_tutorial_notification_content">Learn keyboards shortcuts</string>
+
+ <!-- Notification title for launching touchpad tutorial [CHAR_LIMIT=100] -->
+ <string name="launch_touchpad_tutorial_notification_title">Navigate using your touchpad</string>
+ <!-- Notification text for launching keyboard tutorial [CHAR_LIMIT=100] -->
+ <string name="launch_touchpad_tutorial_notification_content">Learn touchpad gestures</string>
+
+ <!-- Notification title for launching keyboard tutorial [CHAR_LIMIT=100] -->
+ <string name="launch_keyboard_touchpad_tutorial_notification_title">Navigate using your keyboard and touchpad</string>
+ <!-- Notification text for launching keyboard tutorial [CHAR_LIMIT=100] -->
+ <string name="launch_keyboard_touchpad_tutorial_notification_content">Learn touchpad gestures, keyboards shortcuts, and more</string>
+
<!-- TOUCHPAD TUTORIAL-->
<!-- Label for button opening tutorial for back gesture on touchpad [CHAR LIMIT=NONE] -->
<string name="touchpad_tutorial_back_gesture_button">Back gesture</string>
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModel.kt
index 2d57e5b4f204..4fe6fc69e8be 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModel.kt
@@ -22,7 +22,7 @@ import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
+import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.map
@@ -31,11 +31,11 @@ import kotlinx.coroutines.flow.map
* Models UI state for user actions that can lead to navigation to other scenes when showing the
* bouncer scene.
*/
-class BouncerSceneActionsViewModel
+class BouncerUserActionsViewModel
@AssistedInject
constructor(
private val bouncerInteractor: BouncerInteractor,
-) : SceneActionsViewModel() {
+) : UserActionsViewModel() {
override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
bouncerInteractor.dismissDestination
@@ -50,6 +50,6 @@ constructor(
@AssistedFactory
interface Factory {
- fun create(): BouncerSceneActionsViewModel
+ fun create(): BouncerUserActionsViewModel
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
index 87eeebf333e9..3b2d77172393 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
@@ -24,8 +24,6 @@ import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLoggin
import com.android.systemui.contextualeducation.GestureType
import com.android.systemui.contextualeducation.GestureType.ALL_APPS
import com.android.systemui.contextualeducation.GestureType.BACK
-import com.android.systemui.contextualeducation.GestureType.HOME
-import com.android.systemui.contextualeducation.GestureType.OVERVIEW
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
@@ -68,11 +66,9 @@ constructor(
private val keyboardShortcutTriggered: Flow<GestureType> = conflatedCallbackFlow {
val listener = KeyGestureEventListener { event ->
+ // Only store keyboard shortcut time for gestures providing keyboard education
val shortcutType =
when (event.keyGestureType) {
- KeyGestureEvent.KEY_GESTURE_TYPE_BACK -> BACK
- KeyGestureEvent.KEY_GESTURE_TYPE_HOME -> HOME
- KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS -> OVERVIEW
KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS -> ALL_APPS
else -> null
}
@@ -87,6 +83,7 @@ constructor(
}
override fun start() {
+ // Listen to back gesture model changes and trigger education if needed
backgroundScope.launch {
contextualEducationInteractor.backGestureModelFlow.collect {
if (isUsageSessionExpired(it)) {
@@ -98,6 +95,7 @@ constructor(
}
}
+ // Listen to touchpad connection changes and update the first connection time
backgroundScope.launch {
userInputDeviceRepository.isAnyTouchpadConnectedForUser.collect {
if (
@@ -111,6 +109,7 @@ constructor(
}
}
+ // Listen to keyboard connection changes and update the first connection time
backgroundScope.launch {
userInputDeviceRepository.isAnyKeyboardConnectedForUser.collect {
if (
@@ -124,6 +123,7 @@ constructor(
}
}
+ // Listen to keyboard shortcut triggered and update the last trigger time
backgroundScope.launch {
keyboardShortcutTriggered.collect {
contextualEducationInteractor.updateShortcutTriggerTime(it)
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
index 3223433568b9..7821f6940da4 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
@@ -16,11 +16,25 @@
package com.android.systemui.education.domain.interactor
+import android.os.SystemProperties
+import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.contextualeducation.GestureType.ALL_APPS
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
+import com.android.systemui.inputdevice.data.repository.UserInputDeviceRepository
+import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
+import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.KEYBOARD
+import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.TOUCHPAD
+import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository
+import java.time.Clock
import javax.inject.Inject
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.hours
+import kotlin.time.DurationUnit
+import kotlin.time.toDuration
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
/**
@@ -39,12 +53,29 @@ class KeyboardTouchpadEduStatsInteractorImpl
@Inject
constructor(
@Background private val backgroundScope: CoroutineScope,
- private val contextualEducationInteractor: ContextualEducationInteractor
+ private val contextualEducationInteractor: ContextualEducationInteractor,
+ private val inputDeviceRepository: UserInputDeviceRepository,
+ private val tutorialRepository: TutorialSchedulerRepository,
+ @EduClock private val clock: Clock,
) : KeyboardTouchpadEduStatsInteractor {
+ companion object {
+ val initialDelayDuration: Duration
+ get() =
+ SystemProperties.getLong(
+ "persist.contextual_edu.initial_delay_sec",
+ /* defaultValue= */ 72.hours.inWholeSeconds
+ )
+ .toDuration(DurationUnit.SECONDS)
+ }
+
override fun incrementSignalCount(gestureType: GestureType) {
- // Todo: check if keyboard/touchpad is connected before update
- backgroundScope.launch { contextualEducationInteractor.incrementSignalCount(gestureType) }
+ backgroundScope.launch {
+ val targetDevice = getTargetDevice(gestureType)
+ if (isTargetDeviceConnected(targetDevice) && hasInitialDelayElapsed(targetDevice)) {
+ contextualEducationInteractor.incrementSignalCount(gestureType)
+ }
+ }
}
override fun updateShortcutTriggerTime(gestureType: GestureType) {
@@ -52,4 +83,31 @@ constructor(
contextualEducationInteractor.updateShortcutTriggerTime(gestureType)
}
}
+
+ private suspend fun isTargetDeviceConnected(deviceType: DeviceType): Boolean {
+ if (deviceType == KEYBOARD) {
+ return inputDeviceRepository.isAnyKeyboardConnectedForUser.first().isConnected
+ } else if (deviceType == TOUCHPAD) {
+ return inputDeviceRepository.isAnyTouchpadConnectedForUser.first().isConnected
+ }
+ return false
+ }
+
+ /**
+ * Keyboard shortcut education would be provided for All Apps. Touchpad gesture education would
+ * be provided for the rest of the gesture types (i.e. Home, Overview, Back). This method maps
+ * gesture to its target education device.
+ */
+ private fun getTargetDevice(gestureType: GestureType) =
+ when (gestureType) {
+ ALL_APPS -> KEYBOARD
+ else -> TOUCHPAD
+ }
+
+ private suspend fun hasInitialDelayElapsed(deviceType: DeviceType): Boolean {
+ val oobeLaunchTime = tutorialRepository.launchTime(deviceType) ?: return false
+ return clock
+ .instant()
+ .isAfter(oobeLaunchTime.plusSeconds(initialDelayDuration.inWholeSeconds))
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
index e8e1dd4c85d0..7ecacdc7cf16 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
@@ -18,20 +18,20 @@ package com.android.systemui.inputdevice.tutorial
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor
+import com.android.systemui.inputdevice.tutorial.ui.TutorialNotificationCoordinator
import com.android.systemui.shared.Flags.newTouchpadGesturesTutorial
import dagger.Lazy
import javax.inject.Inject
-/** A [CoreStartable] to launch a scheduler for keyboard and touchpad education */
+/** A [CoreStartable] to launch a scheduler for keyboard and touchpad tutorial notification */
@SysUISingleton
class KeyboardTouchpadTutorialCoreStartable
@Inject
-constructor(private val tutorialSchedulerInteractor: Lazy<TutorialSchedulerInteractor>) :
+constructor(private val tutorialNotificationCoordinator: Lazy<TutorialNotificationCoordinator>) :
CoreStartable {
override fun start() {
if (newTouchpadGesturesTutorial()) {
- tutorialSchedulerInteractor.get().start()
+ tutorialNotificationCoordinator.get().start()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
index a8d7dad42a93..cfc913fbc89b 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
@@ -17,9 +17,7 @@
package com.android.systemui.inputdevice.tutorial.domain.interactor
import android.os.SystemProperties
-import android.util.Log
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.KEYBOARD
import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.TOUCHPAD
@@ -31,23 +29,22 @@ import java.time.Instant
import javax.inject.Inject
import kotlin.time.Duration.Companion.hours
import kotlin.time.toKotlinDuration
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.launch
/**
- * When the first time a keyboard or touchpad is connected, wait for [LAUNCH_DELAY], then launch the
- * tutorial as soon as there's a connected device
+ * When the first time a keyboard or touchpad is connected, wait for [LAUNCH_DELAY], and as soon as
+ * there's a connected device, show a notification to launch the tutorial.
*/
@SysUISingleton
class TutorialSchedulerInteractor
@Inject
constructor(
- @Background private val backgroundScope: CoroutineScope,
keyboardRepository: KeyboardRepository,
touchpadRepository: TouchpadRepository,
private val repo: TutorialSchedulerRepository
@@ -58,17 +55,6 @@ constructor(
TOUCHPAD to touchpadRepository.isAnyTouchpadConnected
)
- fun start() {
- backgroundScope.launch {
- // Merging two flows to ensure that launch tutorial is launched consecutively in order
- // to avoid race condition
- merge(touchpadScheduleFlow, keyboardScheduleFlow).collect {
- val tutorialType = resolveTutorialType(it)
- launchTutorial(tutorialType)
- }
- }
- }
-
private val touchpadScheduleFlow = flow {
if (!repo.isLaunched(TOUCHPAD)) {
schedule(TOUCHPAD)
@@ -95,14 +81,19 @@ constructor(
private suspend fun waitForDeviceConnection(deviceType: DeviceType) =
isAnyDeviceConnected[deviceType]!!.filter { it }.first()
- private suspend fun launchTutorial(tutorialType: TutorialType) {
- if (tutorialType == TutorialType.KEYBOARD || tutorialType == TutorialType.BOTH)
- repo.updateLaunchTime(KEYBOARD, Instant.now())
- if (tutorialType == TutorialType.TOUCHPAD || tutorialType == TutorialType.BOTH)
- repo.updateLaunchTime(TOUCHPAD, Instant.now())
- // TODO: launch tutorial
- Log.d(TAG, "Launch tutorial for $tutorialType")
- }
+ // Merging two flows ensures that tutorial is launched consecutively to avoid race condition
+ val tutorials: Flow<TutorialType> =
+ merge(touchpadScheduleFlow, keyboardScheduleFlow).map {
+ val tutorialType = resolveTutorialType(it)
+
+ // TODO: notifying time is not oobe launching time - move these updates into oobe
+ if (tutorialType == TutorialType.KEYBOARD || tutorialType == TutorialType.BOTH)
+ repo.updateLaunchTime(KEYBOARD, Instant.now())
+ if (tutorialType == TutorialType.TOUCHPAD || tutorialType == TutorialType.BOTH)
+ repo.updateLaunchTime(TOUCHPAD, Instant.now())
+
+ tutorialType
+ }
private suspend fun resolveTutorialType(deviceType: DeviceType): TutorialType {
// Resolve the type of tutorial depending on which device are connected when the tutorial is
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
new file mode 100644
index 000000000000..5d9dda3899cd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2024 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.inputdevice.tutorial.ui
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.core.app.NotificationCompat
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor
+import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor.Companion.TAG
+import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor.TutorialType
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_BOTH
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEYBOARD
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_TOUCHPAD
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/** When the scheduler is due, show a notification to launch tutorial */
+@SysUISingleton
+class TutorialNotificationCoordinator
+@Inject
+constructor(
+ @Background private val backgroundScope: CoroutineScope,
+ @Application private val context: Context,
+ private val tutorialSchedulerInteractor: TutorialSchedulerInteractor,
+ private val notificationManager: NotificationManager
+) {
+ fun start() {
+ backgroundScope.launch {
+ tutorialSchedulerInteractor.tutorials.collect { showNotification(it) }
+ }
+ }
+
+ // By sharing the same tag and id, we update the content of existing notification instead of
+ // creating multiple notifications
+ private fun showNotification(tutorialType: TutorialType) {
+ if (tutorialType == TutorialType.NONE) return
+
+ if (notificationManager.getNotificationChannel(CHANNEL_ID) == null)
+ createNotificationChannel()
+
+ // Replace "System UI" app name with "Android System"
+ val extras = Bundle()
+ extras.putString(
+ Notification.EXTRA_SUBSTITUTE_APP_NAME,
+ context.getString(com.android.internal.R.string.android_system_label)
+ )
+
+ val info = getNotificationInfo(tutorialType)!!
+ val notification =
+ NotificationCompat.Builder(context, CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_settings)
+ .setContentTitle(info.title)
+ .setContentText(info.text)
+ .setContentIntent(createPendingIntent(info.type))
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .setAutoCancel(true)
+ .addExtras(extras)
+ .build()
+
+ notificationManager.notify(TAG, NOTIFICATION_ID, notification)
+ }
+
+ private fun createNotificationChannel() {
+ val channel =
+ NotificationChannel(
+ CHANNEL_ID,
+ context.getString(com.android.internal.R.string.android_system_label),
+ NotificationManager.IMPORTANCE_DEFAULT
+ )
+ notificationManager.createNotificationChannel(channel)
+ }
+
+ private fun createPendingIntent(tutorialType: String): PendingIntent {
+ val intent =
+ Intent(context, KeyboardTouchpadTutorialActivity::class.java).apply {
+ putExtra(INTENT_TUTORIAL_TYPE_KEY, tutorialType)
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ }
+ return PendingIntent.getActivity(
+ context,
+ /* requestCode= */ 0,
+ intent,
+ PendingIntent.FLAG_IMMUTABLE
+ )
+ }
+
+ private data class NotificationInfo(val title: String, val text: String, val type: String)
+
+ private fun getNotificationInfo(tutorialType: TutorialType): NotificationInfo? =
+ when (tutorialType) {
+ TutorialType.KEYBOARD ->
+ NotificationInfo(
+ context.getString(R.string.launch_keyboard_tutorial_notification_title),
+ context.getString(R.string.launch_keyboard_tutorial_notification_content),
+ INTENT_TUTORIAL_TYPE_KEYBOARD
+ )
+ TutorialType.TOUCHPAD ->
+ NotificationInfo(
+ context.getString(R.string.launch_touchpad_tutorial_notification_title),
+ context.getString(R.string.launch_touchpad_tutorial_notification_content),
+ INTENT_TUTORIAL_TYPE_TOUCHPAD
+ )
+ TutorialType.BOTH ->
+ NotificationInfo(
+ context.getString(
+ R.string.launch_keyboard_touchpad_tutorial_notification_title
+ ),
+ context.getString(
+ R.string.launch_keyboard_touchpad_tutorial_notification_content
+ ),
+ INTENT_TUTORIAL_TYPE_BOTH
+ )
+ TutorialType.NONE -> null
+ }
+
+ companion object {
+ private const val CHANNEL_ID = "TutorialSchedulerNotificationChannel"
+ private const val NOTIFICATION_ID = 5566
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
index 8debe7975197..1adc285e6bb5 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
@@ -54,6 +54,7 @@ constructor(
const val INTENT_TUTORIAL_TYPE_KEY = "tutorial_type"
const val INTENT_TUTORIAL_TYPE_TOUCHPAD = "touchpad"
const val INTENT_TUTORIAL_TYPE_KEYBOARD = "keyboard"
+ const val INTENT_TUTORIAL_TYPE_BOTH = "both"
}
private val vm by
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt
index 2819e617629d..dd47678e5b36 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt
@@ -28,7 +28,7 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
-import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
+import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.util.kotlin.filterValuesNotNull
@@ -40,13 +40,13 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
/** Models UI state and handles user input for the lockscreen scene. */
-class LockscreenSceneActionsViewModel
+class LockscreenUserActionsViewModel
@AssistedInject
constructor(
private val deviceEntryInteractor: DeviceEntryInteractor,
private val communalInteractor: CommunalInteractor,
private val shadeInteractor: ShadeInteractor,
-) : SceneActionsViewModel() {
+) : UserActionsViewModel() {
override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
shadeInteractor.isShadeTouchable
@@ -119,6 +119,6 @@ constructor(
@AssistedFactory
interface Factory {
- fun create(): LockscreenSceneActionsViewModel
+ fun create(): LockscreenUserActionsViewModel
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
index 6ef83e262ac8..b6868c172a9f 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
@@ -21,13 +21,13 @@ import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.scene.shared.model.Overlays
-import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
+import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
/** Models the UI state for the user actions for navigating to other scenes or overlays. */
class NotificationsShadeOverlayActionsViewModel @AssistedInject constructor() :
- SceneActionsViewModel() {
+ UserActionsViewModel() {
override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
setActions(
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt
index 572a0caf49f2..a5c07bc2fdbf 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt
@@ -21,15 +21,15 @@ import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
+import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
/**
* Models the UI state for the user actions that the user can perform to navigate to other scenes.
*/
-class NotificationsShadeSceneActionsViewModel @AssistedInject constructor() :
- SceneActionsViewModel() {
+class NotificationsShadeUserActionsViewModel @AssistedInject constructor() :
+ UserActionsViewModel() {
override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
setActions(
@@ -42,6 +42,6 @@ class NotificationsShadeSceneActionsViewModel @AssistedInject constructor() :
@AssistedFactory
interface Factory {
- fun create(): NotificationsShadeSceneActionsViewModel
+ fun create(): NotificationsShadeUserActionsViewModel
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt
index a264f5142293..f77386dbe91b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt
@@ -39,7 +39,7 @@ import kotlinx.coroutines.launch
/**
* Models UI state needed for rendering the content of the quick settings scene.
*
- * Different from [QuickSettingsSceneActionsViewModel] that models the UI state needed to figure out
+ * Different from [QuickSettingsUserActionsViewModel] that models the UI state needed to figure out
* which user actions can trigger navigation to other scenes.
*/
class QuickSettingsSceneContentViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
index 9538392b845f..61c4c8c0de86 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
@@ -21,13 +21,13 @@ import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.scene.shared.model.Overlays
-import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
+import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
/** Models the UI state for the user actions for navigating to other scenes or overlays. */
class QuickSettingsShadeOverlayActionsViewModel @AssistedInject constructor() :
- SceneActionsViewModel() {
+ UserActionsViewModel() {
override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
setActions(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt
index 518582843401..d01b33b7be59 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt
@@ -22,7 +22,7 @@ import dagger.assisted.AssistedInject
/**
* Models UI state used to render the content of the quick settings shade scene.
*
- * Different from [QuickSettingsShadeSceneActionsViewModel], which only models user actions that can
+ * Different from [QuickSettingsShadeUserActionsViewModel], which only models user actions that can
* be performed to navigate to other scenes.
*/
class QuickSettingsShadeSceneContentViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModel.kt
index 9690aabdba81..d3dc302d44ca 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModel.kt
@@ -21,7 +21,7 @@ import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
+import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.map
@@ -32,11 +32,11 @@ import kotlinx.coroutines.flow.map
* Different from the [QuickSettingsShadeSceneContentViewModel] which models the _content_ of the
* scene.
*/
-class QuickSettingsShadeSceneActionsViewModel
+class QuickSettingsShadeUserActionsViewModel
@AssistedInject
constructor(
val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
-) : SceneActionsViewModel() {
+) : UserActionsViewModel() {
override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
quickSettingsContainerViewModel.editModeViewModel.isEditing
@@ -53,6 +53,6 @@ constructor(
@AssistedFactory
interface Factory {
- fun create(): QuickSettingsShadeSceneActionsViewModel
+ fun create(): QuickSettingsShadeUserActionsViewModel
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModel.kt
index 2bb5dc66bc16..54e5caca107c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModel.kt
@@ -27,7 +27,7 @@ import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.domain.interactor.SceneBackInteractor
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
+import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.Flow
@@ -42,12 +42,12 @@ import kotlinx.coroutines.flow.map
* Different from [QuickSettingsSceneContentViewModel] that models UI state needed for rendering the
* content of the quick settings scene.
*/
-class QuickSettingsSceneActionsViewModel
+class QuickSettingsUserActionsViewModel
@AssistedInject
constructor(
private val qsSceneAdapter: QSSceneAdapter,
sceneBackInteractor: SceneBackInteractor,
-) : SceneActionsViewModel() {
+) : UserActionsViewModel() {
private val backScene: Flow<SceneKey> =
sceneBackInteractor.backScene
@@ -82,6 +82,6 @@ constructor(
@AssistedFactory
interface Factory {
- fun create(): QuickSettingsSceneActionsViewModel
+ fun create(): QuickSettingsUserActionsViewModel
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt
index 7b0e7f4ded58..ea4122a563ab 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt
@@ -29,11 +29,11 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.map
-class GoneSceneActionsViewModel
+class GoneUserActionsViewModel
@AssistedInject
constructor(
private val shadeInteractor: ShadeInteractor,
-) : SceneActionsViewModel() {
+) : UserActionsViewModel() {
override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
shadeInteractor.shadeMode
@@ -69,6 +69,6 @@ constructor(
@AssistedFactory
interface Factory {
- fun create(): GoneSceneActionsViewModel
+ fun create(): GoneUserActionsViewModel
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/UserActionsViewModel.kt
index 076613005959..57628d0f3f40 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/UserActionsViewModel.kt
@@ -25,15 +25,13 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
/**
- * Base class for view-models that need to keep a map of scene actions (also known as "destination
- * scenes") up-to-date.
+ * Base class for view-models that need to keep a map of user actions up-to-date.
*
* Subclasses need only to override [hydrateActions], suspending forever if they need; they don't
* need to worry about resetting the value of [actions] when the view-model is deactivated/canceled,
* this base class takes care of it.
*/
-// TODO(b/363206563): Rename to UserActionsViewModel.
-abstract class SceneActionsViewModel : ExclusiveActivatable() {
+abstract class UserActionsViewModel : ExclusiveActivatable() {
private val _actions = MutableStateFlow<Map<UserAction, UserActionResult>>(emptyMap())
/**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
index 7c707592f5ab..ce4c081358ba 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
@@ -43,7 +43,7 @@ import kotlinx.coroutines.flow.asStateFlow
/**
* Models UI state used to render the content of the shade scene.
*
- * Different from [ShadeSceneActionsViewModel], which only models user actions that can be performed
+ * Different from [ShadeUserActionsViewModel], which only models user actions that can be performed
* to navigate to other scenes.
*/
class ShadeSceneContentViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
index ab719132b93e..f8a850a357f1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
@@ -24,7 +24,7 @@ import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
-import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
+import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import dagger.assisted.AssistedFactory
@@ -36,12 +36,12 @@ import kotlinx.coroutines.flow.combine
*
* Different from the [ShadeSceneContentViewModel] which models the _content_ of the scene.
*/
-class ShadeSceneActionsViewModel
+class ShadeUserActionsViewModel
@AssistedInject
constructor(
private val qsSceneAdapter: QSSceneAdapter,
private val shadeInteractor: ShadeInteractor,
-) : SceneActionsViewModel() {
+) : UserActionsViewModel() {
override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
combine(
@@ -71,6 +71,6 @@ constructor(
@AssistedFactory
interface Factory {
- fun create(): ShadeSceneActionsViewModel
+ fun create(): ShadeUserActionsViewModel
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 28effe909521..8934d8f8a954 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -1280,6 +1280,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
private final class Receiver extends BroadcastReceiver {
+ private static final int STREAM_UNKNOWN = -1;
+
public void init() {
final IntentFilter filter = new IntentFilter();
filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
@@ -1301,30 +1303,39 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
final String action = intent.getAction();
boolean changed = false;
if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
- final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+ STREAM_UNKNOWN);
final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
final int oldLevel = intent
.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
if (D.BUG) Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream
+ " level=" + level + " oldLevel=" + oldLevel);
- changed = updateStreamLevelW(stream, level);
+ if (stream != STREAM_UNKNOWN) {
+ changed = updateStreamLevelW(stream, level);
+ }
} else if (action.equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION)) {
- final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+ STREAM_UNKNOWN);
final int devices = intent
.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_DEVICES, -1);
final int oldDevices = intent
.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_DEVICES, -1);
if (D.BUG) Log.d(TAG, "onReceive STREAM_DEVICES_CHANGED_ACTION stream="
+ stream + " devices=" + devices + " oldDevices=" + oldDevices);
- changed = checkRoutedToBluetoothW(stream);
- changed |= onVolumeChangedW(stream, 0);
+ if (stream != STREAM_UNKNOWN) {
+ changed |= checkRoutedToBluetoothW(stream);
+ changed |= onVolumeChangedW(stream, 0);
+ }
} else if (action.equals(AudioManager.STREAM_MUTE_CHANGED_ACTION)) {
- final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+ STREAM_UNKNOWN);
final boolean muted = intent
.getBooleanExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, false);
if (D.BUG) Log.d(TAG, "onReceive STREAM_MUTE_CHANGED_ACTION stream=" + stream
+ " muted=" + muted);
- changed = updateStreamMuteW(stream, muted);
+ if (stream != STREAM_UNKNOWN) {
+ changed = updateStreamMuteW(stream, muted);
+ }
} else if (action.equals(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED)) {
if (D.BUG) Log.d(TAG, "onReceive ACTION_EFFECTS_SUPPRESSOR_CHANGED");
changed = updateEffectsSuppressorW(mNoMan.getEffectsSuppressor());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
new file mode 100644
index 000000000000..945f95385db2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 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.inputdevice.tutorial.domain.interactor
+
+import android.app.Notification
+import android.app.NotificationManager
+import androidx.annotation.StringRes
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository
+import com.android.systemui.inputdevice.tutorial.ui.TutorialNotificationCoordinator
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.touchpad.data.repository.FakeTouchpadRepository
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.hours
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TutorialNotificationCoordinatorTest : SysuiTestCase() {
+
+ private lateinit var underTest: TutorialNotificationCoordinator
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private val keyboardRepository = FakeKeyboardRepository()
+ private val touchpadRepository = FakeTouchpadRepository()
+ private lateinit var dataStoreScope: CoroutineScope
+ private lateinit var repository: TutorialSchedulerRepository
+ @Mock private lateinit var notificationManager: NotificationManager
+ @Captor private lateinit var notificationCaptor: ArgumentCaptor<Notification>
+ @get:Rule val rule = MockitoJUnit.rule()
+
+ @Before
+ fun setup() {
+ dataStoreScope = CoroutineScope(Dispatchers.Unconfined)
+ repository =
+ TutorialSchedulerRepository(
+ context,
+ dataStoreScope,
+ dataStoreName = "TutorialNotificationCoordinatorTest"
+ )
+ val interactor =
+ TutorialSchedulerInteractor(keyboardRepository, touchpadRepository, repository)
+ underTest =
+ TutorialNotificationCoordinator(
+ testScope.backgroundScope,
+ context,
+ interactor,
+ notificationManager
+ )
+ notificationCaptor = ArgumentCaptor.forClass(Notification::class.java)
+ underTest.start()
+ }
+
+ @After
+ fun clear() {
+ runBlocking { repository.clearDataStore() }
+ dataStoreScope.cancel()
+ }
+
+ @Test
+ fun showKeyboardNotification() =
+ testScope.runTest {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ advanceTimeBy(LAUNCH_DELAY)
+ verifyNotification(
+ R.string.launch_keyboard_tutorial_notification_title,
+ R.string.launch_keyboard_tutorial_notification_content
+ )
+ }
+
+ @Test
+ fun showTouchpadNotification() =
+ testScope.runTest {
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+ advanceTimeBy(LAUNCH_DELAY)
+ verifyNotification(
+ R.string.launch_touchpad_tutorial_notification_title,
+ R.string.launch_touchpad_tutorial_notification_content
+ )
+ }
+
+ @Test
+ fun showKeyboardTouchpadNotification() =
+ testScope.runTest {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+ advanceTimeBy(LAUNCH_DELAY)
+ verifyNotification(
+ R.string.launch_keyboard_touchpad_tutorial_notification_title,
+ R.string.launch_keyboard_touchpad_tutorial_notification_content
+ )
+ }
+
+ @Test
+ fun doNotShowNotification() =
+ testScope.runTest {
+ advanceTimeBy(LAUNCH_DELAY)
+ verify(notificationManager, never()).notify(eq(TAG), eq(NOTIFICATION_ID), any())
+ }
+
+ private fun verifyNotification(@StringRes titleResId: Int, @StringRes contentResId: Int) {
+ verify(notificationManager)
+ .notify(eq(TAG), eq(NOTIFICATION_ID), notificationCaptor.capture())
+ val notification = notificationCaptor.value
+ val actualTitle = notification.getString(Notification.EXTRA_TITLE)
+ val actualContent = notification.getString(Notification.EXTRA_TEXT)
+ assertThat(actualTitle).isEqualTo(context.getString(titleResId))
+ assertThat(actualContent).isEqualTo(context.getString(contentResId))
+ }
+
+ private fun Notification.getString(key: String): String =
+ this.extras?.getCharSequence(key).toString()
+
+ companion object {
+ private const val TAG = "TutorialSchedulerInteractor"
+ private const val NOTIFICATION_ID = 5566
+ private val LAUNCH_DELAY = 72.hours
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
index 432f7af7c29d..650f9dc7f104 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
@@ -32,6 +32,8 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
@@ -63,13 +65,7 @@ class TutorialSchedulerInteractorTest : SysuiTestCase() {
dataStoreName = "TutorialSchedulerInteractorTest"
)
underTest =
- TutorialSchedulerInteractor(
- testScope.backgroundScope,
- keyboardRepository,
- touchpadRepository,
- schedulerRepository
- )
- underTest.start()
+ TutorialSchedulerInteractor(keyboardRepository, touchpadRepository, schedulerRepository)
}
@After
@@ -81,80 +77,90 @@ class TutorialSchedulerInteractorTest : SysuiTestCase() {
@Test
fun connectKeyboard_delayElapse_launchForKeyboard() =
testScope.runTest {
+ launchAndAssert(TutorialType.KEYBOARD)
+
keyboardRepository.setIsAnyKeyboardConnected(true)
advanceTimeBy(LAUNCH_DELAY)
- assertLaunch(TutorialType.KEYBOARD)
}
@Test
fun connectBothDevices_delayElapse_launchForBoth() =
testScope.runTest {
+ launchAndAssert(TutorialType.BOTH)
+
keyboardRepository.setIsAnyKeyboardConnected(true)
touchpadRepository.setIsAnyTouchpadConnected(true)
advanceTimeBy(LAUNCH_DELAY)
- assertLaunch(TutorialType.BOTH)
}
@Test
fun connectBothDevice_delayNotElapse_launchNothing() =
testScope.runTest {
+ launchAndAssert(TutorialType.NONE)
+
keyboardRepository.setIsAnyKeyboardConnected(true)
touchpadRepository.setIsAnyTouchpadConnected(true)
advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
- assertLaunch(TutorialType.NONE)
}
@Test
fun nothingConnect_delayElapse_launchNothing() =
testScope.runTest {
+ launchAndAssert(TutorialType.NONE)
+
keyboardRepository.setIsAnyKeyboardConnected(false)
touchpadRepository.setIsAnyTouchpadConnected(false)
advanceTimeBy(LAUNCH_DELAY)
- assertLaunch(TutorialType.NONE)
}
@Test
fun connectKeyboard_thenTouchpad_delayElapse_launchForBoth() =
testScope.runTest {
+ launchAndAssert(TutorialType.BOTH)
+
keyboardRepository.setIsAnyKeyboardConnected(true)
advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
touchpadRepository.setIsAnyTouchpadConnected(true)
advanceTimeBy(REMAINING_TIME)
- assertLaunch(TutorialType.BOTH)
}
@Test
fun connectKeyboard_thenTouchpad_removeKeyboard_delayElapse_launchNothing() =
testScope.runTest {
+ launchAndAssert(TutorialType.NONE)
+
keyboardRepository.setIsAnyKeyboardConnected(true)
advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
touchpadRepository.setIsAnyTouchpadConnected(true)
keyboardRepository.setIsAnyKeyboardConnected(false)
advanceTimeBy(REMAINING_TIME)
- assertLaunch(TutorialType.NONE)
}
- // TODO: likely to be changed after we update TutorialSchedulerInteractor.launchTutorial
- private suspend fun assertLaunch(tutorialType: TutorialType) {
- when (tutorialType) {
- TutorialType.KEYBOARD -> {
- assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isTrue()
- assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isFalse()
- }
- TutorialType.TOUCHPAD -> {
- assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isFalse()
- assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isTrue()
- }
- TutorialType.BOTH -> {
- assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isTrue()
- assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isTrue()
- }
- TutorialType.NONE -> {
- assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isFalse()
- assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isFalse()
+ private suspend fun launchAndAssert(expectedTutorial: TutorialType) =
+ testScope.backgroundScope.launch {
+ val actualTutorial = underTest.tutorials.first()
+ assertThat(actualTutorial).isEqualTo(expectedTutorial)
+
+ // TODO: need to update after we move launch into the tutorial
+ when (expectedTutorial) {
+ TutorialType.KEYBOARD -> {
+ assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isTrue()
+ assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isFalse()
+ }
+ TutorialType.TOUCHPAD -> {
+ assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isFalse()
+ assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isTrue()
+ }
+ TutorialType.BOTH -> {
+ assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isTrue()
+ assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isTrue()
+ }
+ TutorialType.NONE -> {
+ assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isFalse()
+ assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isFalse()
+ }
}
}
- }
companion object {
private val LAUNCH_DELAY = 72.hours
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index a0d231b8bb6f..60a185537b0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -51,6 +51,7 @@ import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.service.notification.StatusBarNotification;
import android.view.ViewGroup;
+import android.widget.ImageView;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -431,6 +432,32 @@ public class StatusBarIconViewTest extends SysuiTestCase {
mIconView.getIconScale(), 0.01f);
}
+ @Test
+ @EnableFlags({Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS})
+ public void set_iconThatWantsFixedSpace_setsScaleType() {
+ mIconView.setScaleType(ImageView.ScaleType.FIT_START);
+ StatusBarIcon icon = new StatusBarIcon(UserHandle.ALL, "mockPackage",
+ Icon.createWithResource(mContext, R.drawable.ic_android), 0, 0, "",
+ StatusBarIcon.Type.SystemIcon, StatusBarIcon.Shape.FIXED_SPACE);
+
+ mIconView.set(icon);
+
+ assertThat(mIconView.getScaleType()).isEqualTo(ImageView.ScaleType.FIT_CENTER);
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS})
+ public void set_iconWithOtherShape_keepsScaleType() {
+ mIconView.setScaleType(ImageView.ScaleType.FIT_START);
+ StatusBarIcon icon = new StatusBarIcon(UserHandle.ALL, "mockPackage",
+ Icon.createWithResource(mContext, R.drawable.ic_android), 0, 0, "",
+ StatusBarIcon.Type.SystemIcon, StatusBarIcon.Shape.WRAP_CONTENT);
+
+ mIconView.set(icon);
+
+ assertThat(mIconView.getScaleType()).isEqualTo(ImageView.ScaleType.FIT_START);
+ }
+
private static StatusBarNotification getMockSbn() {
StatusBarNotification sbn = mock(StatusBarNotification.class);
when(sbn.getNotification()).thenReturn(mock(Notification.class));
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
index 171be97bf964..649e4e8a6f7e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -33,16 +33,16 @@ import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.StateFlow
-val Kosmos.bouncerSceneActionsViewModel by Fixture {
- BouncerSceneActionsViewModel(
+val Kosmos.bouncerUserActionsViewModel by Fixture {
+ BouncerUserActionsViewModel(
bouncerInteractor = bouncerInteractor,
)
}
-val Kosmos.bouncerSceneActionsViewModelFactory by Fixture {
- object : BouncerSceneActionsViewModel.Factory {
- override fun create(): BouncerSceneActionsViewModel {
- return bouncerSceneActionsViewModel
+val Kosmos.bouncerUserActionsViewModelFactory by Fixture {
+ object : BouncerUserActionsViewModel.Factory {
+ override fun create(): BouncerUserActionsViewModel {
+ return bouncerUserActionsViewModel
}
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
index 811c6533c656..7ccacb66e124 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.education.domain.interactor
import android.hardware.input.InputManager
import com.android.systemui.education.data.repository.fakeEduClock
import com.android.systemui.inputdevice.data.repository.UserInputDeviceRepository
+import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository
import com.android.systemui.keyboard.data.repository.keyboardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
@@ -50,6 +51,12 @@ var Kosmos.keyboardTouchpadEduStatsInteractor by
Kosmos.Fixture {
KeyboardTouchpadEduStatsInteractorImpl(
backgroundScope = testScope.backgroundScope,
- contextualEducationInteractor = contextualEducationInteractor
+ contextualEducationInteractor = contextualEducationInteractor,
+ inputDeviceRepository = mockUserInputDeviceRepository,
+ tutorialRepository = mockTutorialSchedulerRepository,
+ clock = fakeEduClock
)
}
+
+var mockUserInputDeviceRepository = mock<UserInputDeviceRepository>()
+var mockTutorialSchedulerRepository = mock<TutorialSchedulerRepository>()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
index 128a7fcbab25..06592b1ea3ed 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
@@ -18,9 +18,9 @@ package com.android.systemui.qs.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
-val Kosmos.quickSettingsShadeSceneActionsViewModel: QuickSettingsShadeSceneActionsViewModel by
+val Kosmos.quickSettingsShadeUserActionsViewModel: QuickSettingsShadeUserActionsViewModel by
Kosmos.Fixture {
- QuickSettingsShadeSceneActionsViewModel(
+ QuickSettingsShadeUserActionsViewModel(
quickSettingsContainerViewModel = quickSettingsContainerViewModel,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeUserActionsViewModelKosmos.kt
index f792ab95e8ac..6345c4076412 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeUserActionsViewModelKosmos.kt
@@ -18,7 +18,7 @@ package com.android.systemui.shade.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneActionsViewModel
+import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeUserActionsViewModel
-val Kosmos.notificationsShadeSceneActionsViewModel:
- NotificationsShadeSceneActionsViewModel by Fixture { NotificationsShadeSceneActionsViewModel() }
+val Kosmos.notificationsShadeUserActionsViewModel:
+ NotificationsShadeUserActionsViewModel by Fixture { NotificationsShadeUserActionsViewModel() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelKosmos.kt
index 2387aa856fe6..48c5121c71c1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelKosmos.kt
@@ -21,8 +21,8 @@ import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.qs.ui.adapter.qsSceneAdapter
import com.android.systemui.shade.domain.interactor.shadeInteractor
-val Kosmos.shadeSceneActionsViewModel: ShadeSceneActionsViewModel by Fixture {
- ShadeSceneActionsViewModel(
+val Kosmos.shadeUserActionsViewModel: ShadeUserActionsViewModel by Fixture {
+ ShadeUserActionsViewModel(
qsSceneAdapter = qsSceneAdapter,
shadeInteractor = shadeInteractor,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
index 1fa6c3f2327b..888351f0a882 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
@@ -33,6 +33,12 @@ import kotlinx.coroutines.flow.update
class FakeAudioRepository : AudioRepository {
+ private val unMutableStreams =
+ setOf(
+ AudioManager.STREAM_VOICE_CALL,
+ AudioManager.STREAM_ALARM,
+ )
+
private val mutableMode = MutableStateFlow(AudioManager.MODE_NORMAL)
override val mode: StateFlow<Int> = mutableMode.asStateFlow()
@@ -73,7 +79,7 @@ class FakeAudioRepository : AudioRepository {
volume = 0,
minVolume = 0,
maxVolume = 10,
- isAffectedByMute = false,
+ isAffectedByMute = audioStream.value !in unMutableStreams,
isAffectedByRingerMode = false,
isMuted = false,
)
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index d16a66522e51..1b2447e2c58a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -26,6 +26,7 @@ import android.annotation.MainThread;
import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Region;
+import android.hardware.input.InputManager;
import android.os.Looper;
import android.os.PowerManager;
import android.os.SystemClock;
@@ -57,6 +58,7 @@ import com.android.server.policy.WindowManagerPolicy;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Objects;
import java.util.StringJoiner;
/**
@@ -748,6 +750,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
if ((mEnabledFeatures & FLAG_FEATURE_MOUSE_KEYS) != 0) {
mMouseKeysInterceptor = new MouseKeysInterceptor(mAms,
+ Objects.requireNonNull(mContext.getSystemService(InputManager.class)),
Looper.myLooper(),
Display.DEFAULT_DISPLAY);
addFirstEventHandler(Display.DEFAULT_DISPLAY, mMouseKeysInterceptor);
diff --git a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
index 2ce5c2bc3790..54368ca9c03e 100644
--- a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
+++ b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
@@ -23,6 +23,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.VirtualDeviceParams;
+import android.hardware.input.InputManager;
import android.hardware.input.VirtualMouse;
import android.hardware.input.VirtualMouseButtonEvent;
import android.hardware.input.VirtualMouseConfig;
@@ -34,8 +35,11 @@ import android.os.Message;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import android.view.InputDevice;
import android.view.KeyEvent;
+import androidx.annotation.VisibleForTesting;
+
import com.android.server.LocalServices;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
@@ -60,7 +64,7 @@ import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
* mouse keys of each physical keyboard will control a single (global) mouse pointer.
*/
public class MouseKeysInterceptor extends BaseEventStreamTransformation
- implements Handler.Callback {
+ implements Handler.Callback, InputManager.InputDeviceListener {
private static final String LOG_TAG = "MouseKeysInterceptor";
// To enable these logs, run: 'adb shell setprop log.tag.MouseKeysInterceptor DEBUG'
@@ -77,10 +81,19 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
private final AccessibilityManagerService mAms;
private final Handler mHandler;
+ private final InputManager mInputManager;
/** Thread to wait for virtual mouse creation to complete */
private final Thread mCreateVirtualMouseThread;
+ /**
+ * Map of device IDs to a map of key codes to their corresponding {@link MouseKeyEvent} values.
+ * To ensure thread safety for the map, all access and modification of the map
+ * should happen on the same thread, i.e., on the handler thread.
+ */
+ private final SparseArray<SparseArray<MouseKeyEvent>> mDeviceKeyCodeMap =
+ new SparseArray<>();
+
VirtualDeviceManager.VirtualDevice mVirtualDevice = null;
private VirtualMouse mVirtualMouse = null;
@@ -102,6 +115,21 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
/** Whether scroll toggle is on */
private boolean mScrollToggleOn = false;
+ /** The ID of the input device that is currently active */
+ private int mActiveInputDeviceId = 0;
+
+ /**
+ * Enum representing different types of mouse key events, each associated with a specific
+ * key code.
+ *
+ * <p> These events correspond to various mouse actions such as directional movements,
+ * clicks, and scrolls, mapped to specific keys on the keyboard.
+ * The key codes here are the QWERTY key codes, and should be accessed via
+ * {@link MouseKeyEvent#getKeyCode(InputDevice)}
+ * so that it is mapped to the equivalent key on the keyboard layout of the keyboard device
+ * that is actually in use.
+ * </p>
+ */
public enum MouseKeyEvent {
DIAGONAL_UP_LEFT_MOVE(KeyEvent.KEYCODE_7),
UP_MOVE_OR_SCROLL(KeyEvent.KEYCODE_8),
@@ -117,34 +145,64 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
RELEASE(KeyEvent.KEYCODE_COMMA),
SCROLL_TOGGLE(KeyEvent.KEYCODE_PERIOD);
- private final int mKeyCode;
+ private final int mLocationKeyCode;
MouseKeyEvent(int enumValue) {
- mKeyCode = enumValue;
+ mLocationKeyCode = enumValue;
}
- private static final SparseArray<MouseKeyEvent> VALUE_TO_ENUM_MAP = new SparseArray<>();
-
- static {
- for (MouseKeyEvent type : MouseKeyEvent.values()) {
- VALUE_TO_ENUM_MAP.put(type.mKeyCode, type);
- }
+ @VisibleForTesting
+ public final int getKeyCodeValue() {
+ return mLocationKeyCode;
}
- public final int getKeyCodeValue() {
- return mKeyCode;
+ /**
+ * Get the key code associated with the given MouseKeyEvent for the given keyboard
+ * input device, taking into account its layout.
+ * The default is to return the keycode for the default layout (QWERTY).
+ * We check if the input device has been generated using {@link InputDevice#getGeneration()}
+ * to test with the default {@link MouseKeyEvent} values in the unit tests.
+ */
+ public int getKeyCode(InputDevice inputDevice) {
+ if (inputDevice.getGeneration() == -1) {
+ return mLocationKeyCode;
+ }
+ return inputDevice.getKeyCodeForKeyLocation(mLocationKeyCode);
}
/**
- * Convert int value of the key code to corresponding MouseEvent enum. If no matching
- * value is found, this will return {@code null}.
+ * Convert int value of the key code to corresponding {@link MouseKeyEvent}
+ * enum for a particular device ID.
+ * If no matching value is found, this will return {@code null}.
*/
@Nullable
- public static MouseKeyEvent from(int value) {
- return VALUE_TO_ENUM_MAP.get(value);
+ public static MouseKeyEvent from(int keyCode, int deviceId,
+ SparseArray<SparseArray<MouseKeyEvent>> deviceKeyCodeMap) {
+ SparseArray<MouseKeyEvent> keyCodeToEnumMap = deviceKeyCodeMap.get(deviceId);
+ if (keyCodeToEnumMap != null) {
+ return keyCodeToEnumMap.get(keyCode);
+ }
+ return null;
}
}
/**
+ * Create a map of key codes to their corresponding {@link MouseKeyEvent} values
+ * for a specific input device.
+ * The key for {@code mDeviceKeyCodeMap} is the deviceId.
+ * The key for {@code keyCodeToEnumMap} is the keycode for each
+ * {@link MouseKeyEvent} according to the keyboard layout of the input device.
+ */
+ public void initializeDeviceToEnumMap(InputDevice inputDevice) {
+ int deviceId = inputDevice.getId();
+ SparseArray<MouseKeyEvent> keyCodeToEnumMap = new SparseArray<>();
+ for (MouseKeyEvent mouseKeyEventType : MouseKeyEvent.values()) {
+ int keyCode = mouseKeyEventType.getKeyCode(inputDevice);
+ keyCodeToEnumMap.put(keyCode, mouseKeyEventType);
+ }
+ mDeviceKeyCodeMap.put(deviceId, keyCodeToEnumMap);
+ }
+
+ /**
* Construct a new MouseKeysInterceptor.
*
* @param service The service to notify of key events
@@ -152,8 +210,10 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
* @param displayId Display ID to send mouse events to
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
- public MouseKeysInterceptor(AccessibilityManagerService service, Looper looper, int displayId) {
+ public MouseKeysInterceptor(AccessibilityManagerService service,
+ InputManager inputManager, Looper looper, int displayId) {
mAms = service;
+ mInputManager = inputManager;
mHandler = new Handler(looper, this);
// Create the virtual mouse on a separate thread since virtual device creation
// should happen on an auxiliary thread, and not from the handler's thread.
@@ -163,6 +223,9 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
mVirtualMouse = createVirtualMouse(displayId);
});
mCreateVirtualMouseThread.start();
+ // Register an input device listener to watch when input devices are
+ // added, removed or reconfigured.
+ mInputManager.registerInputDeviceListener(this, mHandler);
}
/**
@@ -215,7 +278,8 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
private void performMouseScrollAction(int keyCode) {
- MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(keyCode);
+ MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(
+ keyCode, mActiveInputDeviceId, mDeviceKeyCodeMap);
float y = switch (mouseKeyEvent) {
case UP_MOVE_OR_SCROLL -> 1.0f;
case DOWN_MOVE_OR_SCROLL -> -1.0f;
@@ -247,15 +311,18 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
private void performMouseButtonAction(int keyCode) {
- MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(keyCode);
+ MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(
+ keyCode, mActiveInputDeviceId, mDeviceKeyCodeMap);
int buttonCode = switch (mouseKeyEvent) {
case LEFT_CLICK -> VirtualMouseButtonEvent.BUTTON_PRIMARY;
case RIGHT_CLICK -> VirtualMouseButtonEvent.BUTTON_SECONDARY;
default -> VirtualMouseButtonEvent.BUTTON_UNKNOWN;
};
if (buttonCode != VirtualMouseButtonEvent.BUTTON_UNKNOWN) {
- sendVirtualMouseButtonEvent(buttonCode, VirtualMouseButtonEvent.ACTION_BUTTON_PRESS);
- sendVirtualMouseButtonEvent(buttonCode, VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE);
+ sendVirtualMouseButtonEvent(buttonCode,
+ VirtualMouseButtonEvent.ACTION_BUTTON_PRESS);
+ sendVirtualMouseButtonEvent(buttonCode,
+ VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE);
}
if (DEBUG) {
if (buttonCode == VirtualMouseButtonEvent.BUTTON_UNKNOWN) {
@@ -293,7 +360,9 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
private void performMousePointerAction(int keyCode) {
float x = 0f;
float y = 0f;
- MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(keyCode);
+ MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(
+ keyCode, mActiveInputDeviceId, mDeviceKeyCodeMap);
+
switch (mouseKeyEvent) {
case DIAGONAL_DOWN_LEFT_MOVE -> {
x = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
@@ -339,18 +408,19 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
}
}
- private boolean isMouseKey(int keyCode) {
- return MouseKeyEvent.VALUE_TO_ENUM_MAP.contains(keyCode);
+ private boolean isMouseKey(int keyCode, int deviceId) {
+ SparseArray<MouseKeyEvent> keyCodeToEnumMap = mDeviceKeyCodeMap.get(deviceId);
+ return keyCodeToEnumMap.contains(keyCode);
}
- private boolean isMouseButtonKey(int keyCode) {
- return keyCode == MouseKeyEvent.LEFT_CLICK.getKeyCodeValue()
- || keyCode == MouseKeyEvent.RIGHT_CLICK.getKeyCodeValue();
+ private boolean isMouseButtonKey(int keyCode, InputDevice inputDevice) {
+ return keyCode == MouseKeyEvent.LEFT_CLICK.getKeyCode(inputDevice)
+ || keyCode == MouseKeyEvent.RIGHT_CLICK.getKeyCode(inputDevice);
}
- private boolean isMouseScrollKey(int keyCode) {
- return keyCode == MouseKeyEvent.UP_MOVE_OR_SCROLL.getKeyCodeValue()
- || keyCode == MouseKeyEvent.DOWN_MOVE_OR_SCROLL.getKeyCodeValue();
+ private boolean isMouseScrollKey(int keyCode, InputDevice inputDevice) {
+ return keyCode == MouseKeyEvent.UP_MOVE_OR_SCROLL.getKeyCode(inputDevice)
+ || keyCode == MouseKeyEvent.DOWN_MOVE_OR_SCROLL.getKeyCode(inputDevice);
}
/**
@@ -373,7 +443,7 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
}
/**
- * Handles key events and forwards mouse key events to the virtual mouse.
+ * Handles key events and forwards mouse key events to the virtual mouse on the handler thread.
*
* @param event The key event to handle.
* @param policyFlags The policy flags associated with the key event.
@@ -385,31 +455,45 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
mAms.getTraceManager().logTrace(LOG_TAG + ".onKeyEvent",
FLAGS_INPUT_FILTER, "event=" + event + ";policyFlags=" + policyFlags);
}
+
+ mHandler.post(() -> {
+ onKeyEventInternal(event, policyFlags);
+ });
+ }
+
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ private void onKeyEventInternal(KeyEvent event, int policyFlags) {
boolean isDown = event.getAction() == KeyEvent.ACTION_DOWN;
int keyCode = event.getKeyCode();
+ mActiveInputDeviceId = event.getDeviceId();
+ InputDevice inputDevice = mInputManager.getInputDevice(mActiveInputDeviceId);
- if (!isMouseKey(keyCode)) {
+ if (!mDeviceKeyCodeMap.contains(mActiveInputDeviceId)) {
+ initializeDeviceToEnumMap(inputDevice);
+ }
+
+ if (!isMouseKey(keyCode, mActiveInputDeviceId)) {
// Pass non-mouse key events to the next handler
super.onKeyEvent(event, policyFlags);
} else if (isDown) {
- if (keyCode == MouseKeyEvent.SCROLL_TOGGLE.getKeyCodeValue()) {
+ if (keyCode == MouseKeyEvent.SCROLL_TOGGLE.getKeyCode(inputDevice)) {
mScrollToggleOn = !mScrollToggleOn;
if (DEBUG) {
Slog.d(LOG_TAG, "Scroll toggle " + (mScrollToggleOn ? "ON" : "OFF"));
}
- } else if (keyCode == MouseKeyEvent.HOLD.getKeyCodeValue()) {
+ } else if (keyCode == MouseKeyEvent.HOLD.getKeyCode(inputDevice)) {
sendVirtualMouseButtonEvent(
VirtualMouseButtonEvent.BUTTON_PRIMARY,
VirtualMouseButtonEvent.ACTION_BUTTON_PRESS
);
- } else if (keyCode == MouseKeyEvent.RELEASE.getKeyCodeValue()) {
+ } else if (keyCode == MouseKeyEvent.RELEASE.getKeyCode(inputDevice)) {
sendVirtualMouseButtonEvent(
VirtualMouseButtonEvent.BUTTON_PRIMARY,
VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE
);
- } else if (isMouseButtonKey(keyCode)) {
+ } else if (isMouseButtonKey(keyCode, inputDevice)) {
performMouseButtonAction(keyCode);
- } else if (mScrollToggleOn && isMouseScrollKey(keyCode)) {
+ } else if (mScrollToggleOn && isMouseScrollKey(keyCode, inputDevice)) {
// If the scroll key is pressed down and no other key is active,
// set it as the active key and send a message to scroll the pointer
if (mActiveScrollKey == KEY_NOT_SET) {
@@ -439,7 +523,8 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
mHandler.removeMessages(MESSAGE_SCROLL_MOUSE_POINTER);
} else {
Slog.i(LOG_TAG, "Dropping event with key code: '" + keyCode
- + "', with no matching down event from deviceId = " + event.getDeviceId());
+ + "', with no matching down event from deviceId = "
+ + event.getDeviceId());
}
}
}
@@ -503,12 +588,40 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Override
public void onDestroy() {
- // Clear mouse state
- mActiveMoveKey = KEY_NOT_SET;
- mActiveScrollKey = KEY_NOT_SET;
- mLastTimeKeyActionPerformed = 0;
+ mHandler.post(() -> {
+ // Clear mouse state
+ mActiveMoveKey = KEY_NOT_SET;
+ mActiveScrollKey = KEY_NOT_SET;
+ mLastTimeKeyActionPerformed = 0;
+ mDeviceKeyCodeMap.clear();
+ });
mHandler.removeCallbacksAndMessages(null);
mVirtualDevice.close();
}
+
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+ mDeviceKeyCodeMap.remove(deviceId);
+ }
+
+ /**
+ * The user can change the keyboard layout from settings at anytime, which would change
+ * key character map for that device. Hence, we should use this callback to
+ * update the key code to enum mapping if there is a change in the physical keyboard detected.
+ *
+ * @param deviceId The id of the input device that changed.
+ */
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+ InputDevice inputDevice = mInputManager.getInputDevice(deviceId);
+ // Update the enum mapping only if input device that changed is a keyboard
+ if (inputDevice.isFullKeyboard() && !mDeviceKeyCodeMap.contains(deviceId)) {
+ initializeDeviceToEnumMap(inputDevice);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 99ad65d14ff2..0abd9bc3a433 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -354,7 +354,7 @@ public final class DisplayManagerService extends SystemService {
new CopyOnWriteArrayList<>();
/** All {@link DisplayPowerController}s indexed by {@link LogicalDisplay} ID. */
- private final SparseArray<DisplayPowerControllerInterface> mDisplayPowerControllers =
+ private final SparseArray<DisplayPowerController> mDisplayPowerControllers =
new SparseArray<>();
/** {@link DisplayBlanker} used by all {@link DisplayPowerController}s. */
@@ -726,7 +726,7 @@ public final class DisplayManagerService extends SystemService {
if (logicalDisplay.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) {
return;
}
- final DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(
+ final DisplayPowerController dpc = mDisplayPowerControllers.get(
logicalDisplay.getDisplayIdLocked());
if (dpc == null) {
return;
@@ -2058,7 +2058,7 @@ public final class DisplayManagerService extends SystemService {
configurePreferredDisplayModeLocked(display);
}
- DisplayPowerControllerInterface dpc = addDisplayPowerControllerLocked(display);
+ DisplayPowerController dpc = addDisplayPowerControllerLocked(display);
if (dpc != null) {
final int leadDisplayId = display.getLeadDisplayIdLocked();
updateDisplayPowerControllerLeaderLocked(dpc, leadDisplayId);
@@ -2067,7 +2067,7 @@ public final class DisplayManagerService extends SystemService {
// that the follower display was added before the lead display.
mLogicalDisplayMapper.forEachLocked(d -> {
if (d.getLeadDisplayIdLocked() == displayId) {
- DisplayPowerControllerInterface followerDpc =
+ DisplayPowerController followerDpc =
mDisplayPowerControllers.get(d.getDisplayIdLocked());
if (followerDpc != null) {
updateDisplayPowerControllerLeaderLocked(followerDpc, displayId);
@@ -2151,7 +2151,7 @@ public final class DisplayManagerService extends SystemService {
scheduleTraversalLocked(false);
mPersistentDataStore.saveIfNeeded();
- DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId);
+ DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
if (dpc != null) {
final int leadDisplayId = display.getLeadDisplayIdLocked();
updateDisplayPowerControllerLeaderLocked(dpc, leadDisplayId);
@@ -2165,7 +2165,7 @@ public final class DisplayManagerService extends SystemService {
}
private void updateDisplayPowerControllerLeaderLocked(
- @NonNull DisplayPowerControllerInterface dpc, int leadDisplayId) {
+ @NonNull DisplayPowerController dpc, int leadDisplayId) {
if (dpc.getLeadDisplayId() == leadDisplayId) {
// Lead display hasn't changed, nothing to do.
return;
@@ -2174,7 +2174,7 @@ public final class DisplayManagerService extends SystemService {
// If it has changed, then we need to unregister from the previous leader if there was one.
final int prevLeaderId = dpc.getLeadDisplayId();
if (prevLeaderId != Layout.NO_LEAD_DISPLAY) {
- final DisplayPowerControllerInterface prevLeader =
+ final DisplayPowerController prevLeader =
mDisplayPowerControllers.get(prevLeaderId);
if (prevLeader != null) {
prevLeader.removeDisplayBrightnessFollower(dpc);
@@ -2183,7 +2183,7 @@ public final class DisplayManagerService extends SystemService {
// And then, if it's following, register it with the new one.
if (leadDisplayId != Layout.NO_LEAD_DISPLAY) {
- final DisplayPowerControllerInterface newLeader =
+ final DisplayPowerController newLeader =
mDisplayPowerControllers.get(leadDisplayId);
if (newLeader != null) {
newLeader.addDisplayBrightnessFollower(dpc);
@@ -2224,7 +2224,7 @@ public final class DisplayManagerService extends SystemService {
private void releaseDisplayAndEmitEvent(LogicalDisplay display, int event) {
final int displayId = display.getDisplayIdLocked();
- final DisplayPowerControllerInterface dpc =
+ final DisplayPowerController dpc =
mDisplayPowerControllers.removeReturnOld(displayId);
if (dpc != null) {
updateDisplayPowerControllerLeaderLocked(dpc, Layout.NO_LEAD_DISPLAY);
@@ -2271,7 +2271,7 @@ public final class DisplayManagerService extends SystemService {
private void handleLogicalDisplayDeviceStateTransitionLocked(@NonNull LogicalDisplay display) {
final int displayId = display.getDisplayIdLocked();
- final DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId);
+ final DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
if (dpc != null) {
final int leadDisplayId = display.getLeadDisplayIdLocked();
updateDisplayPowerControllerLeaderLocked(dpc, leadDisplayId);
@@ -2692,14 +2692,14 @@ public final class DisplayManagerService extends SystemService {
if (userId != mCurrentUserId) {
return;
}
- DisplayPowerControllerInterface dpc = getDpcFromUniqueIdLocked(uniqueId);
+ DisplayPowerController dpc = getDpcFromUniqueIdLocked(uniqueId);
if (dpc != null) {
dpc.setBrightnessConfiguration(c, /* shouldResetShortTermModel= */ true);
}
}
}
- private DisplayPowerControllerInterface getDpcFromUniqueIdLocked(String uniqueId) {
+ private DisplayPowerController getDpcFromUniqueIdLocked(String uniqueId) {
final DisplayDevice displayDevice = mDisplayDeviceRepo.getByUniqueIdLocked(uniqueId);
final LogicalDisplay logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayDevice);
if (logicalDisplay != null) {
@@ -2740,7 +2740,7 @@ public final class DisplayManagerService extends SystemService {
final BrightnessConfiguration config =
getBrightnessConfigForDisplayWithPdsFallbackLocked(uniqueId, userSerial);
if (config != null) {
- final DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(
+ final DisplayPowerController dpc = mDisplayPowerControllers.get(
logicalDisplay.getDisplayIdLocked());
if (dpc != null) {
dpc.setBrightnessConfiguration(config,
@@ -2987,7 +2987,7 @@ public final class DisplayManagerService extends SystemService {
void setAutoBrightnessLoggingEnabled(boolean enabled) {
synchronized (mSyncRoot) {
- final DisplayPowerControllerInterface displayPowerController =
+ final DisplayPowerController displayPowerController =
mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY);
if (displayPowerController != null) {
displayPowerController.setAutoBrightnessLoggingEnabled(enabled);
@@ -2997,7 +2997,7 @@ public final class DisplayManagerService extends SystemService {
void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
synchronized (mSyncRoot) {
- final DisplayPowerControllerInterface displayPowerController =
+ final DisplayPowerController displayPowerController =
mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY);
if (displayPowerController != null) {
displayPowerController.setDisplayWhiteBalanceLoggingEnabled(enabled);
@@ -3023,7 +3023,7 @@ public final class DisplayManagerService extends SystemService {
void setAmbientColorTemperatureOverride(float cct) {
synchronized (mSyncRoot) {
- final DisplayPowerControllerInterface displayPowerController =
+ final DisplayPowerController displayPowerController =
mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY);
if (displayPowerController != null) {
displayPowerController.setAmbientColorTemperatureOverride(cct);
@@ -3033,7 +3033,7 @@ public final class DisplayManagerService extends SystemService {
void setDockedAndIdleEnabled(boolean enabled, int displayId) {
synchronized (mSyncRoot) {
- final DisplayPowerControllerInterface displayPowerController =
+ final DisplayPowerController displayPowerController =
mDisplayPowerControllers.get(displayId);
if (displayPowerController != null) {
displayPowerController.setAutomaticScreenBrightnessMode(enabled
@@ -3571,7 +3571,7 @@ public final class DisplayManagerService extends SystemService {
}
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
- private DisplayPowerControllerInterface addDisplayPowerControllerLocked(
+ private DisplayPowerController addDisplayPowerControllerLocked(
LogicalDisplay display) {
if (mPowerHandler == null) {
// initPowerManagement has not yet been called.
@@ -3585,7 +3585,7 @@ public final class DisplayManagerService extends SystemService {
final int userSerial = getUserManager().getUserSerialNumber(mContext.getUserId());
final BrightnessSetting brightnessSetting = new BrightnessSetting(userSerial,
mPersistentDataStore, display, mSyncRoot);
- final DisplayPowerControllerInterface displayPowerController;
+ final DisplayPowerController displayPowerController;
// If display is internal and has a HighBrightnessModeMetadata mapping, use that.
// Or create a new one and use that.
@@ -4373,7 +4373,7 @@ public final class DisplayManagerService extends SystemService {
uniqueId, userSerial);
if (config == null) {
// Get default configuration
- DisplayPowerControllerInterface dpc = getDpcFromUniqueIdLocked(uniqueId);
+ DisplayPowerController dpc = getDpcFromUniqueIdLocked(uniqueId);
if (dpc != null) {
config = dpc.getDefaultBrightnessConfiguration();
}
@@ -4427,7 +4427,7 @@ public final class DisplayManagerService extends SystemService {
if (display == null || !display.isEnabledLocked()) {
return null;
}
- DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId);
+ DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
if (dpc != null) {
return dpc.getBrightnessInfo();
}
@@ -4472,7 +4472,7 @@ public final class DisplayManagerService extends SystemService {
final long token = Binder.clearCallingIdentity();
try {
synchronized (mSyncRoot) {
- DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId);
+ DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
if (dpc != null) {
dpc.setBrightness(brightness);
}
@@ -4492,7 +4492,7 @@ public final class DisplayManagerService extends SystemService {
final long token = Binder.clearCallingIdentity();
try {
synchronized (mSyncRoot) {
- DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId);
+ DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
if (dpc != null) {
brightness = dpc.getScreenBrightnessSetting();
}
@@ -4819,7 +4819,7 @@ public final class DisplayManagerService extends SystemService {
id).getPrimaryDisplayDeviceLocked();
final int flags = displayDevice.getDisplayDeviceInfoLocked().flags;
if ((flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
- final DisplayPowerControllerInterface displayPowerController =
+ final DisplayPowerController displayPowerController =
mDisplayPowerControllers.get(id);
if (displayPowerController != null) {
ready &= displayPowerController.requestPowerState(request,
@@ -5200,7 +5200,7 @@ public final class DisplayManagerService extends SystemService {
return null;
}
- DisplayPowerControllerInterface displayPowerController =
+ DisplayPowerController displayPowerController =
mDisplayPowerControllers.get(logicalDisplay.getDisplayIdLocked());
if (displayPowerController == null) {
Slog.w(TAG,
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index a188e79e4c8b..b05a96ea0439 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -39,12 +39,12 @@ public class DisplayOffloadSessionImpl implements DisplayManagerInternal.Display
@Nullable
private final DisplayManagerInternal.DisplayOffloader mDisplayOffloader;
- private final DisplayPowerControllerInterface mDisplayPowerController;
+ private final DisplayPowerController mDisplayPowerController;
private boolean mIsActive;
public DisplayOffloadSessionImpl(
@Nullable DisplayManagerInternal.DisplayOffloader displayOffloader,
- DisplayPowerControllerInterface displayPowerController) {
+ DisplayPowerController displayPowerController) {
mDisplayOffloader = displayOffloader;
mDisplayPowerController = displayPowerController;
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index bf559c10b0ba..bb2bed7281f7 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -126,7 +126,7 @@ import java.util.Objects;
* slower by changing the "animator duration scale" option in Development Settings.
*/
final class DisplayPowerController implements AutomaticBrightnessController.Callbacks,
- DisplayWhiteBalanceController.Callbacks, DisplayPowerControllerInterface {
+ DisplayWhiteBalanceController.Callbacks{
private static final String SCREEN_ON_BLOCKED_TRACE_NAME = "Screen on blocked";
private static final String SCREEN_OFF_BLOCKED_TRACE_NAME = "Screen off blocked";
@@ -481,7 +481,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// DPCs following the brightness of this DPC. This is used in concurrent displays mode - there
// is one lead display, the additional displays follow the brightness value of the lead display.
@GuardedBy("mLock")
- private final SparseArray<DisplayPowerControllerInterface> mDisplayBrightnessFollowers =
+ private final SparseArray<DisplayPowerController> mDisplayBrightnessFollowers =
new SparseArray();
private boolean mBootCompleted;
@@ -679,7 +679,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
/**
* Returns true if the proximity sensor screen-off function is available.
*/
- @Override
public boolean isProximitySensorAvailable() {
return mDisplayPowerProximityStateController.isProximitySensorAvailable();
}
@@ -691,7 +690,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
* @param includePackage if false will null out the package name in events
*/
@Nullable
- @Override
public ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents(
@UserIdInt int userId, boolean includePackage) {
if (mBrightnessTracker == null) {
@@ -700,7 +698,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
return mBrightnessTracker.getEvents(userId, includePackage);
}
- @Override
public void onSwitchUser(@UserIdInt int newUserId, int userSerial, float newBrightness) {
Message msg = mHandler.obtainMessage(MSG_SWITCH_USER, newUserId, userSerial, newBrightness);
mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
@@ -737,7 +734,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
@Nullable
- @Override
public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(
@UserIdInt int userId) {
if (mBrightnessTracker == null) {
@@ -749,7 +745,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
/**
* Persist the brightness slider events and ambient brightness stats to disk.
*/
- @Override
public void persistBrightnessTrackerState() {
if (mBrightnessTracker != null) {
mBrightnessTracker.persistBrightnessTrackerState();
@@ -806,7 +801,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
}
- @Override
public void overrideDozeScreenState(int displayState, @Display.StateReason int reason) {
Slog.i(TAG, "New offload doze override: " + Display.stateToString(displayState));
if (mDisplayOffloadSession != null
@@ -833,7 +827,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
}
- @Override
public void setDisplayOffloadSession(DisplayOffloadSession session) {
if (session == mDisplayOffloadSession) {
return;
@@ -842,7 +835,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mDisplayOffloadSession = session;
}
- @Override
public BrightnessConfiguration getDefaultBrightnessConfiguration() {
if (mAutomaticBrightnessController == null) {
return null;
@@ -857,7 +849,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
*
* Make sure DisplayManagerService.mSyncRoot lock is held when this is called
*/
- @Override
public void onDisplayChanged(HighBrightnessModeMetadata hbmMetadata, int leadDisplayId) {
mLeadDisplayId = leadDisplayId;
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
@@ -939,7 +930,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
* This method should be called when the DisplayPowerController is no longer in use; i.e. when
* the {@link #mDisplayId display} has been removed.
*/
- @Override
public void stop() {
synchronized (mLock) {
clearDisplayBrightnessFollowersLocked();
@@ -1216,7 +1206,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
}
- @Override
public void setAutomaticScreenBrightnessMode(
@AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
Message msg = mHandler.obtainMessage();
@@ -1314,7 +1303,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
boolean mustInitialize = false;
mBrightnessReasonTemp.set(null);
mTempBrightnessEvent.reset();
- SparseArray<DisplayPowerControllerInterface> displayBrightnessFollowers;
+ SparseArray<DisplayPowerController> displayBrightnessFollowers;
synchronized (mLock) {
if (mStopped) {
return;
@@ -1547,7 +1536,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
float ambientLux = mAutomaticBrightnessController == null ? 0
: mAutomaticBrightnessController.getAmbientLux();
for (int i = 0; i < displayBrightnessFollowers.size(); i++) {
- DisplayPowerControllerInterface follower = displayBrightnessFollowers.valueAt(i);
+ DisplayPowerController follower = displayBrightnessFollowers.valueAt(i);
follower.setBrightnessToFollow(rawBrightnessState,
mDisplayBrightnessController.convertToNits(rawBrightnessState),
ambientLux, slowChange);
@@ -1904,7 +1893,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
}
- @Override
public void updateBrightness() {
sendUpdatePowerState();
}
@@ -1913,12 +1901,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
* Ignores the proximity sensor until the sensor state changes, but only if the sensor is
* currently enabled and forcing the screen to be dark.
*/
- @Override
public void ignoreProximitySensorUntilChanged() {
mDisplayPowerProximityStateController.ignoreProximitySensorUntilChanged();
}
- @Override
public void setBrightnessConfiguration(BrightnessConfiguration c,
boolean shouldResetShortTermModel) {
Message msg = mHandler.obtainMessage(MSG_CONFIGURE_BRIGHTNESS,
@@ -1926,28 +1912,24 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
msg.sendToTarget();
}
- @Override
public void setTemporaryBrightness(float brightness) {
Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_BRIGHTNESS,
Float.floatToIntBits(brightness), 0 /*unused*/);
msg.sendToTarget();
}
- @Override
public void setTemporaryAutoBrightnessAdjustment(float adjustment) {
Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT,
Float.floatToIntBits(adjustment), 0 /*unused*/);
msg.sendToTarget();
}
- @Override
public void setBrightnessFromOffload(float brightness) {
Message msg = mHandler.obtainMessage(MSG_SET_BRIGHTNESS_FROM_OFFLOAD,
Float.floatToIntBits(brightness), 0 /*unused*/);
mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
- @Override
public float[] getAutoBrightnessLevels(
@AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
@@ -1956,7 +1938,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
}
- @Override
public float[] getAutoBrightnessLuxLevels(
@AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
@@ -1965,7 +1946,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
}
- @Override
public BrightnessInfo getBrightnessInfo() {
synchronized (mCachedBrightnessInfo) {
return new BrightnessInfo(
@@ -1979,7 +1959,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
}
- @Override
public void onBootCompleted() {
Message msg = mHandler.obtainMessage(MSG_BOOT_COMPLETED);
mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
@@ -2495,18 +2474,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
}
-
- @Override
public float getScreenBrightnessSetting() {
return mDisplayBrightnessController.getScreenBrightnessSetting();
}
- @Override
public float getDozeBrightnessForOffload() {
return mDisplayBrightnessController.getCurrentBrightness() * mDozeScaleFactor;
}
- @Override
public void setBrightness(float brightness) {
// After HBMController and NBMController migration to Clampers framework
// currentBrightnessMax should be taken from clampers controller
@@ -2515,7 +2490,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mBrightnessRangeController.getCurrentBrightnessMax());
}
- @Override
public void setBrightness(float brightness, int userSerial) {
// After HBMController and NBMController migration to Clampers framework
// currentBrightnessMax should be taken from clampers controller
@@ -2524,17 +2498,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mBrightnessRangeController.getCurrentBrightnessMax());
}
- @Override
public int getDisplayId() {
return mDisplayId;
}
- @Override
public int getLeadDisplayId() {
return mLeadDisplayId;
}
- @Override
public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
boolean slowChange) {
mBrightnessRangeController.onAmbientLuxChange(ambientLux);
@@ -2595,16 +2566,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mAutomaticBrightnessController.getLastSensorTimestamps());
}
- @Override
- public void addDisplayBrightnessFollower(DisplayPowerControllerInterface follower) {
+ public void addDisplayBrightnessFollower(DisplayPowerController follower) {
synchronized (mLock) {
mDisplayBrightnessFollowers.append(follower.getDisplayId(), follower);
sendUpdatePowerStateLocked();
}
}
- @Override
- public void removeDisplayBrightnessFollower(DisplayPowerControllerInterface follower) {
+ public void removeDisplayBrightnessFollower(DisplayPowerController follower) {
synchronized (mLock) {
mDisplayBrightnessFollowers.remove(follower.getDisplayId());
mHandler.postAtTime(() -> follower.setBrightnessToFollow(
@@ -2616,7 +2585,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
@GuardedBy("mLock")
private void clearDisplayBrightnessFollowersLocked() {
for (int i = 0; i < mDisplayBrightnessFollowers.size(); i++) {
- DisplayPowerControllerInterface follower = mDisplayBrightnessFollowers.valueAt(i);
+ DisplayPowerController follower = mDisplayBrightnessFollowers.valueAt(i);
mHandler.postAtTime(() -> follower.setBrightnessToFollow(
PowerManager.BRIGHTNESS_INVALID_FLOAT, BrightnessMappingStrategy.INVALID_NITS,
/* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
@@ -2624,7 +2593,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mDisplayBrightnessFollowers.clear();
}
- @Override
public void dump(final PrintWriter pw) {
synchronized (mLock) {
pw.println();
@@ -3161,19 +3129,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
}
- @Override
public void setAutoBrightnessLoggingEnabled(boolean enabled) {
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.setLoggingEnabled(enabled);
}
}
- @Override // DisplayWhiteBalanceController.Callbacks
+ // DisplayWhiteBalanceController.Callbacks
public void updateWhiteBalance() {
sendUpdatePowerState();
}
- @Override
public void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
Message msg = mHandler.obtainMessage();
msg.what = MSG_SET_DWBC_LOGGING_ENABLED;
@@ -3181,7 +3147,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
msg.sendToTarget();
}
- @Override
public void setAmbientColorTemperatureOverride(float cct) {
Message msg = mHandler.obtainMessage();
msg.what = MSG_SET_DWBC_COLOR_OVERRIDE;
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
deleted file mode 100644
index d28578ad571f..000000000000
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display;
-
-import android.content.pm.ParceledListSlice;
-import android.hardware.display.AmbientBrightnessDayStats;
-import android.hardware.display.BrightnessChangeEvent;
-import android.hardware.display.BrightnessConfiguration;
-import android.hardware.display.BrightnessInfo;
-import android.hardware.display.DisplayManagerInternal;
-import android.os.PowerManager;
-import android.view.Display;
-
-import java.io.PrintWriter;
-
-/**
- * An interface to manage the display's power state and brightness
- */
-public interface DisplayPowerControllerInterface {
- /**
- * Notified when the display is changed.
- *
- * We use this to apply any changes that might be needed when displays get swapped on foldable
- * devices, when layouts change, etc.
- *
- * Must be called while holding the SyncRoot lock.
- *
- * @param hbmInfo The high brightness mode metadata, like
- * remaining time and hbm events, for the corresponding
- * physical display, to make sure we stay within the safety margins.
- * @param leadDisplayId The display who is considered our "leader" for things like brightness.
- */
- void onDisplayChanged(HighBrightnessModeMetadata hbmInfo, int leadDisplayId);
-
- /**
- * Unregisters all listeners and interrupts all running threads; halting future work.
- *
- * This method should be called when the DisplayPowerController is no longer in use; i.e. when
- * the display has been removed.
- */
- void stop();
-
- /**
- * Used to update the display's BrightnessConfiguration
- * @param config The new BrightnessConfiguration
- */
- void setBrightnessConfiguration(BrightnessConfiguration config,
- boolean shouldResetShortTermModel);
-
- /**
- * Used to set the ambient color temperature of the Display
- * @param ambientColorTemperature The target ambientColorTemperature
- */
- void setAmbientColorTemperatureOverride(float ambientColorTemperature);
-
- /**
- * Used to decide the associated AutomaticBrightnessController's BrightnessMode
- * @param mode The auto-brightness mode
- */
- void setAutomaticScreenBrightnessMode(
- @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
-
- /**
- * Used to enable/disable the logging of the WhileBalance associated entities
- * @param enabled Flag which represents if the logging is the be enabled
- */
- void setDisplayWhiteBalanceLoggingEnabled(boolean enabled);
-
- /**
- * Used to dump the state.
- * @param writer The PrintWriter used to dump the state.
- */
- void dump(PrintWriter writer);
-
- /**
- * Used to get the ambient brightness stats
- */
- ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(int userId);
-
- /**
- * Get the default brightness configuration
- */
- BrightnessConfiguration getDefaultBrightnessConfiguration();
-
- /**
- * Set the screen brightness of the associated display
- * @param brightness The value to which the brightness is to be set
- */
- void setBrightness(float brightness);
-
- /**
- * Set the screen brightness of the associated display
- * @param brightness The value to which the brightness is to be set
- * @param userSerial The user for which the brightness value is to be set.
- */
- void setBrightness(float brightness, int userSerial);
-
- /**
- * Checks if the proximity sensor is available
- */
- boolean isProximitySensorAvailable();
-
- /**
- * Persist the brightness slider events and ambient brightness stats to disk.
- */
- void persistBrightnessTrackerState();
-
- /**
- * Ignores the proximity sensor until the sensor state changes, but only if the sensor is
- * currently enabled and forcing the screen to be dark.
- */
- void ignoreProximitySensorUntilChanged();
-
- /**
- * Requests a new power state.
- *
- * @param request The requested power state.
- * @param waitForNegativeProximity If true, issues a request to wait for
- * negative proximity before turning the screen back on,
- * assuming the screen was turned off by the proximity sensor.
- * @return True if display is ready, false if there are important changes that must
- * be made asynchronously.
- */
- boolean requestPowerState(DisplayManagerInternal.DisplayPowerRequest request,
- boolean waitForNegativeProximity);
-
- /**
- * Overrides the current doze screen state.
- *
- * @param displayState the new doze display state.
- * @param reason the reason behind the new doze display state.
- */
- void overrideDozeScreenState(int displayState, @Display.StateReason int reason);
-
- void setDisplayOffloadSession(DisplayManagerInternal.DisplayOffloadSession session);
-
- /**
- * Sets up the temporary autobrightness adjustment when the user is yet to settle down to a
- * value.
- */
- void setTemporaryAutoBrightnessAdjustment(float adjustment);
-
- /**
- * Sets temporary brightness from the offload chip until we get a brightness value from
- * the light sensor.
- * @param brightness The brightness value between {@link PowerManager.BRIGHTNESS_MIN} and
- * {@link PowerManager.BRIGHTNESS_MAX}. Values outside of that range will be ignored.
- */
- void setBrightnessFromOffload(float brightness);
-
- /**
- * Gets the screen brightness setting
- */
- float getScreenBrightnessSetting();
-
- /**
- * Gets the brightness value used when the device is in doze
- */
- float getDozeBrightnessForOffload();
-
- /**
- * Sets up the temporary brightness for the associated display
- */
- void setTemporaryBrightness(float brightness);
-
- /**
- * Gets the associated {@link BrightnessInfo}
- */
- BrightnessInfo getBrightnessInfo();
-
- /**
- * Get the {@link BrightnessChangeEvent}s for the specified user.
- */
- ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents(int userId, boolean hasUsageStats);
-
- /**
- * Sets up the logging for the associated {@link AutomaticBrightnessController}
- * @param enabled Flag to represent if the logging is to be enabled
- */
- void setAutoBrightnessLoggingEnabled(boolean enabled);
-
- /**
- * Handles the changes to be done to update the brightness when the user is changed
- * @param newUserId The new userId
- * @param userSerial The serial number of the new user
- * @param newBrightness The brightness for the new user
- */
- void onSwitchUser(int newUserId, int userSerial, float newBrightness);
-
- /**
- * Get the ID of the display associated with this DPC.
- * @return The display ID
- */
- int getDisplayId();
-
- /**
- * Get the ID of the display that is the leader of this DPC.
- *
- * Note that this is different than the display associated with the DPC. The leader is another
- * display which we follow for things like brightness.
- *
- * Must be called while holding the SyncRoot lock.
- */
- int getLeadDisplayId();
-
- /**
- * Set the brightness to follow if this is an additional display in a set of concurrent
- * displays.
- * @param leadDisplayBrightness The brightness of the lead display in the set of concurrent
- * displays
- * @param nits The brightness value in nits if the device supports nits. Set to a negative
- * number otherwise.
- * @param ambientLux The lux value that will be passed to {@link HighBrightnessModeController}
- * @param slowChange Indicates whether we should slowly animate to the given brightness value.
- */
- void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
- boolean slowChange);
-
- /**
- * Add an additional display that will copy the brightness value from this display. This is used
- * when the device is in concurrent displays mode.
- * @param follower The DPC that should copy the brightness value from this DPC
- */
- void addDisplayBrightnessFollower(DisplayPowerControllerInterface follower);
-
- /**
- * Removes the given display from the list of brightness followers.
- * @param follower The DPC to remove from the followers list
- */
- void removeDisplayBrightnessFollower(DisplayPowerControllerInterface follower);
-
- /**
- * Indicate that boot has been completed and the screen is ready to update.
- */
- void onBootCompleted();
-
- /**
- * Get the brightness levels used to determine automatic brightness based on lux levels.
- * @param mode The auto-brightness mode
- * @return The brightness levels for the specified mode. The values are between
- * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
- */
- float[] getAutoBrightnessLevels(
- @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
-
- /**
- * Get the lux levels used to determine automatic brightness.
- * @param mode The auto-brightness mode
- * @return The lux levels for the specified mode
- */
- float[] getAutoBrightnessLuxLevels(
- @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
-}
diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
index 5fca771c48b9..7785ffb4b17a 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
@@ -16,23 +16,69 @@
package com.android.server.input.debug;
+import android.annotation.NonNull;
import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.util.Slog;
import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.TextView;
-public class TouchpadDebugView extends LinearLayout {
+import java.util.Objects;
+public class TouchpadDebugView extends LinearLayout {
/**
* Input device ID for the touchpad that this debug view is displaying.
*/
private final int mTouchpadId;
+ @NonNull
+ private final WindowManager mWindowManager;
+
+ @NonNull
+ private final WindowManager.LayoutParams mWindowLayoutParams;
+
+ private final int mTouchSlop;
+
+ private float mTouchDownX;
+ private float mTouchDownY;
+ private int mScreenWidth;
+ private int mScreenHeight;
+ private int mWindowLocationBeforeDragX;
+ private int mWindowLocationBeforeDragY;
+
public TouchpadDebugView(Context context, int touchpadId) {
super(context);
mTouchpadId = touchpadId;
+ mWindowManager =
+ Objects.requireNonNull(getContext().getSystemService(WindowManager.class));
init(context);
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+
+ // TODO(b/360137366): Use the hardware properties to initialise layout parameters.
+ mWindowLayoutParams = new WindowManager.LayoutParams();
+ mWindowLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+ mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+ mWindowLayoutParams.privateFlags |=
+ WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ mWindowLayoutParams.setFitInsetsTypes(0);
+ mWindowLayoutParams.layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ mWindowLayoutParams.format = PixelFormat.TRANSLUCENT;
+ mWindowLayoutParams.setTitle("TouchpadDebugView - display " + mContext.getDisplayId());
+
+ mWindowLayoutParams.x = 40;
+ mWindowLayoutParams.y = 100;
+ mWindowLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ mWindowLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ mWindowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
}
private void init(Context context) {
@@ -43,14 +89,12 @@ public class TouchpadDebugView extends LinearLayout {
setBackgroundColor(Color.TRANSPARENT);
// TODO(b/286551975): Replace this content with the touchpad debug view.
-
TextView textView1 = new TextView(context);
textView1.setBackgroundColor(Color.parseColor("#FFFF0000"));
textView1.setTextSize(20);
textView1.setText("Touchpad Debug View 1");
textView1.setGravity(Gravity.CENTER);
textView1.setTextColor(Color.WHITE);
-
textView1.setLayoutParams(new LayoutParams(1000, 200));
TextView textView2 = new TextView(context);
@@ -63,9 +107,98 @@ public class TouchpadDebugView extends LinearLayout {
addView(textView1);
addView(textView2);
+
+ updateScreenDimensions();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ float deltaX;
+ float deltaY;
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mWindowLocationBeforeDragX = mWindowLayoutParams.x;
+ mWindowLocationBeforeDragY = mWindowLayoutParams.y;
+ mTouchDownX = event.getRawX() - mWindowLocationBeforeDragX;
+ mTouchDownY = event.getRawY() - mWindowLocationBeforeDragY;
+ return true;
+
+ case MotionEvent.ACTION_MOVE:
+ deltaX = event.getRawX() - mWindowLayoutParams.x - mTouchDownX;
+ deltaY = event.getRawY() - mWindowLayoutParams.y - mTouchDownY;
+ Slog.d("TouchpadDebugView", "Slop = " + mTouchSlop);
+ if (isSlopExceeded(deltaX, deltaY)) {
+ Slog.d("TouchpadDebugView", "Slop exceeded");
+ mWindowLayoutParams.x =
+ Math.max(0, Math.min((int) (event.getRawX() - mTouchDownX),
+ mScreenWidth - this.getWidth()));
+ mWindowLayoutParams.y =
+ Math.max(0, Math.min((int) (event.getRawY() - mTouchDownY),
+ mScreenHeight - this.getHeight()));
+
+ Slog.d("TouchpadDebugView", "New position X: "
+ + mWindowLayoutParams.x + ", Y: " + mWindowLayoutParams.y);
+
+ mWindowManager.updateViewLayout(this, mWindowLayoutParams);
+ }
+ return true;
+
+ case MotionEvent.ACTION_UP:
+ deltaX = event.getRawX() - mWindowLayoutParams.x - mTouchDownX;
+ deltaY = event.getRawY() - mWindowLayoutParams.y - mTouchDownY;
+ if (!isSlopExceeded(deltaX, deltaY)) {
+ performClick();
+ }
+ return true;
+
+ case MotionEvent.ACTION_CANCEL:
+ // Move the window back to the original position
+ mWindowLayoutParams.x = mWindowLocationBeforeDragX;
+ mWindowLayoutParams.y = mWindowLocationBeforeDragY;
+ mWindowManager.updateViewLayout(this, mWindowLayoutParams);
+ return true;
+
+ default:
+ return super.onTouchEvent(event);
+ }
+ }
+
+ @Override
+ public boolean performClick() {
+ super.performClick();
+ Slog.d("TouchpadDebugView", "You clicked me!");
+ return true;
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ updateScreenDimensions();
+
+ // Adjust view position to stay within screen bounds after rotation
+ mWindowLayoutParams.x =
+ Math.max(0, Math.min(mWindowLayoutParams.x, mScreenWidth - getWidth()));
+ mWindowLayoutParams.y =
+ Math.max(0, Math.min(mWindowLayoutParams.y, mScreenHeight - getHeight()));
+ mWindowManager.updateViewLayout(this, mWindowLayoutParams);
+ }
+
+ private boolean isSlopExceeded(float deltaX, float deltaY) {
+ return deltaX * deltaX + deltaY * deltaY >= mTouchSlop * mTouchSlop;
+ }
+
+ private void updateScreenDimensions() {
+ Rect windowBounds =
+ mWindowManager.getCurrentWindowMetrics().getBounds();
+ mScreenWidth = windowBounds.width();
+ mScreenHeight = windowBounds.height();
}
public int getTouchpadId() {
return mTouchpadId;
}
+
+ public WindowManager.LayoutParams getWindowLayoutParams() {
+ return mWindowLayoutParams;
+ }
}
diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
index c7760c63fec5..1e59167fdb9f 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
@@ -18,14 +18,12 @@ package com.android.server.input.debug;
import android.annotation.Nullable;
import android.content.Context;
-import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.Looper;
import android.util.Slog;
import android.view.Display;
-import android.view.Gravity;
import android.view.InputDevice;
import android.view.WindowManager;
@@ -36,12 +34,14 @@ import java.util.Objects;
public class TouchpadDebugViewController {
- private static final String TAG = "TouchpadDebugViewController";
+ private static final String TAG = "TouchpadDebugView";
private final Context mContext;
private final Handler mHandler;
+
@Nullable
private TouchpadDebugView mTouchpadDebugView;
+
private final InputManagerService mInputManagerService;
public TouchpadDebugViewController(Context context, Looper looper,
@@ -95,32 +95,15 @@ public class TouchpadDebugViewController {
mContext.getSystemService(WindowManager.class));
mTouchpadDebugView = new TouchpadDebugView(mContext, touchpadId);
+ final WindowManager.LayoutParams mWindowLayoutParams =
+ mTouchpadDebugView.getWindowLayoutParams();
- final WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
- lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
- lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
- lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
- lp.setFitInsetsTypes(0);
- lp.layoutInDisplayCutoutMode =
- WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- lp.format = PixelFormat.TRANSLUCENT;
- lp.setTitle("TouchpadDebugView - display " + mContext.getDisplayId());
- lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
-
- lp.x = 40;
- lp.y = 100;
- lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
- lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
- lp.gravity = Gravity.TOP | Gravity.LEFT;
-
- wm.addView(mTouchpadDebugView, lp);
+ wm.addView(mTouchpadDebugView, mWindowLayoutParams);
Slog.d(TAG, "Touchpad debug view created.");
TouchpadHardwareProperties mTouchpadHardwareProperties =
mInputManagerService.getTouchpadHardwareProperties(
touchpadId);
- // TODO(b/360137366): Use the hardware properties to initialise layout parameters.
if (mTouchpadHardwareProperties != null) {
Slog.d(TAG, mTouchpadHardwareProperties.toString());
} else {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index f96706e474be..749025b0cf40 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -16,6 +16,7 @@
package com.android.server.policy;
+import static android.Manifest.permission.CREATE_VIRTUAL_DEVICE;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW;
import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
@@ -59,13 +60,18 @@ import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD;
@@ -3058,7 +3064,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
/** {@inheritDoc} */
@Override
public int checkAddPermission(int type, boolean isRoundedCornerOverlay, String packageName,
- int[] outAppOp) {
+ int[] outAppOp, int displayId) {
if (isRoundedCornerOverlay && mContext.checkCallingOrSelfPermission(INTERNAL_SYSTEM_WINDOW)
!= PERMISSION_GRANTED) {
return ADD_PERMISSION_DENIED;
@@ -3098,6 +3104,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case TYPE_VOICE_INTERACTION:
case TYPE_QS_DIALOG:
case TYPE_NAVIGATION_BAR_PANEL:
+ case TYPE_STATUS_BAR:
+ case TYPE_NOTIFICATION_SHADE:
+ case TYPE_NAVIGATION_BAR:
+ case TYPE_STATUS_BAR_ADDITIONAL:
+ case TYPE_STATUS_BAR_SUB_PANEL:
+ case TYPE_VOICE_INTERACTION_STARTING:
// The window manager will check these.
return ADD_OKAY;
}
@@ -3141,6 +3153,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return ADD_OKAY;
}
+ // Allow virtual device owners to add overlays on the displays they own.
+ if (mWindowManagerFuncs.isCallerVirtualDeviceOwner(displayId, callingUid)
+ && mContext.checkCallingOrSelfPermission(CREATE_VIRTUAL_DEVICE)
+ == PERMISSION_GRANTED) {
+ return ADD_OKAY;
+ }
+
// check if user has enabled this operation. SecurityException will be thrown if this app
// has not been allowed by the user. The reason to use "noteOp" (instead of checkOp) is to
// make sure the usage is logged.
@@ -3581,18 +3600,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (down) {
int direction = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_UP ? 1 : -1;
- // Disable autobrightness if it's on
- int auto = Settings.System.getIntForUser(
- mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL,
- UserHandle.USER_CURRENT_OR_SELF);
- if (auto != 0) {
- Settings.System.putIntForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL,
- UserHandle.USER_CURRENT_OR_SELF);
- }
int screenDisplayId = displayId < 0 ? DEFAULT_DISPLAY : displayId;
float minLinearBrightness = mPowerManager.getBrightnessConstraint(
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 989c8a802b36..892af6bec534 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -362,6 +362,12 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
* Invoked when a screenshot is taken of the given display to notify registered listeners.
*/
List<ComponentName> notifyScreenshotListeners(int displayId);
+
+ /**
+ * Returns whether the given UID is the owner of a virtual device, which the given display
+ * belongs to.
+ */
+ boolean isCallerVirtualDeviceOwner(int displayId, int callingUid);
}
/**
@@ -421,6 +427,7 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
* @param packageName package name
* @param outAppOp First element will be filled with the app op corresponding to
* this window, or OP_NONE.
+ * @param displayId The display on which this window is being added.
*
* @return {@link WindowManagerGlobal#ADD_OKAY} if the add can proceed;
* else an error code, usually
@@ -429,7 +436,7 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
* @see WindowManager.LayoutParams#PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY
*/
int checkAddPermission(int type, boolean isRoundedCornerOverlay, String packageName,
- int[] outAppOp);
+ int[] outAppOp, int displayId);
/**
* After the window manager has computed the current configuration based
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 822ec2eb79b0..6847a5c699ac 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -54,6 +54,7 @@ import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.Temperature;
+import android.os.Trace;
import android.util.ArrayMap;
import android.util.EventLog;
import android.util.Slog;
@@ -247,6 +248,7 @@ public class ThermalManagerService extends SystemService {
private void setStatusLocked(int newStatus) {
if (newStatus != mStatus) {
+ Trace.traceCounter(Trace.TRACE_TAG_POWER, "ThermalManagerService.status", newStatus);
mStatus = newStatus;
notifyStatusListenersLocked();
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index e90a2c90bfab..9a3ad2d85de6 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -107,7 +107,6 @@ import android.app.servertransaction.LaunchActivityItem;
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.ResumeActivityItem;
import android.app.servertransaction.StopActivityItem;
-import android.companion.virtual.VirtualDeviceManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -158,6 +157,7 @@ import com.android.server.LocalServices;
import com.android.server.am.ActivityManagerService;
import com.android.server.am.HostingRecord;
import com.android.server.am.UserState;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.pm.SaferIntentUtils;
import com.android.server.utils.Slogf;
import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
@@ -285,7 +285,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
private WindowManagerService mWindowManager;
private AppOpsManager mAppOpsManager;
- private VirtualDeviceManager mVirtualDeviceManager;
+ private VirtualDeviceManagerInternal mVirtualDeviceManagerInternal;
/** Common synchronization logic used to save things to disks. */
PersisterQueue mPersisterQueue;
@@ -1298,16 +1298,24 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) {
return Context.DEVICE_ID_DEFAULT;
}
- if (mVirtualDeviceManager == null) {
+ if (mVirtualDeviceManagerInternal == null) {
if (mService.mHasCompanionDeviceSetupFeature) {
- mVirtualDeviceManager =
- mService.mContext.getSystemService(VirtualDeviceManager.class);
+ mVirtualDeviceManagerInternal =
+ LocalServices.getService(VirtualDeviceManagerInternal.class);
}
- if (mVirtualDeviceManager == null) {
+ if (mVirtualDeviceManagerInternal == null) {
return Context.DEVICE_ID_DEFAULT;
}
}
- return mVirtualDeviceManager.getDeviceIdForDisplayId(displayId);
+ return mVirtualDeviceManagerInternal.getDeviceIdForDisplayId(displayId);
+ }
+
+ boolean isDeviceOwnerUid(int displayId, int callingUid) {
+ final int deviceId = getDeviceIdForDisplayId(displayId);
+ if (deviceId == Context.DEVICE_ID_DEFAULT || deviceId == Context.DEVICE_ID_INVALID) {
+ return false;
+ }
+ return mVirtualDeviceManagerInternal.getDeviceOwnerUid(deviceId) == callingUid;
}
private AppOpsManager getAppOpsManager() {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 745b79209546..5c621208c4db 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1036,7 +1036,7 @@ public class DisplayPolicy {
/**
* Check if a window can be added to the system.
*
- * Currently enforces that two window types are singletons per display:
+ * Currently enforces that these window types are singletons per display:
* <ul>
* <li>{@link WindowManager.LayoutParams#TYPE_STATUS_BAR}</li>
* <li>{@link WindowManager.LayoutParams#TYPE_NOTIFICATION_SHADE}</li>
@@ -1058,41 +1058,39 @@ public class DisplayPolicy {
ActivityTaskManagerService.enforceTaskPermission("DisplayPolicy");
}
+ final String systemUiPermission =
+ mService.isCallerVirtualDeviceOwner(mDisplayContent.getDisplayId(), callingUid)
+ // Allow virtual device owners to add system windows on their displays.
+ ? android.Manifest.permission.CREATE_VIRTUAL_DEVICE
+ : android.Manifest.permission.STATUS_BAR_SERVICE;
+
switch (attrs.type) {
case TYPE_STATUS_BAR:
- mContext.enforcePermission(
- android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
+ mContext.enforcePermission(systemUiPermission, callingPid, callingUid,
"DisplayPolicy");
if (mStatusBar != null && mStatusBar.isAlive()) {
return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
}
break;
case TYPE_NOTIFICATION_SHADE:
- mContext.enforcePermission(
- android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
+ mContext.enforcePermission(systemUiPermission, callingPid, callingUid,
"DisplayPolicy");
- if (mNotificationShade != null) {
- if (mNotificationShade.isAlive()) {
- return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
- }
+ if (mNotificationShade != null && mNotificationShade.isAlive()) {
+ return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
}
break;
case TYPE_NAVIGATION_BAR:
- mContext.enforcePermission(android.Manifest.permission.STATUS_BAR_SERVICE,
- callingPid, callingUid, "DisplayPolicy");
+ mContext.enforcePermission(systemUiPermission, callingPid, callingUid,
+ "DisplayPolicy");
if (mNavigationBar != null && mNavigationBar.isAlive()) {
return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
}
break;
case TYPE_NAVIGATION_BAR_PANEL:
- mContext.enforcePermission(android.Manifest.permission.STATUS_BAR_SERVICE,
- callingPid, callingUid, "DisplayPolicy");
- break;
case TYPE_STATUS_BAR_ADDITIONAL:
case TYPE_STATUS_BAR_SUB_PANEL:
case TYPE_VOICE_INTERACTION_STARTING:
- mContext.enforcePermission(
- android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
+ mContext.enforcePermission(systemUiPermission, callingPid, callingUid,
"DisplayPolicy");
break;
case TYPE_STATUS_BAR_PANEL:
@@ -1102,8 +1100,7 @@ public class DisplayPolicy {
if (attrs.providedInsets != null) {
// Recents component is allowed to add inset types.
if (!mService.mAtmService.isCallerRecents(callingUid)) {
- mContext.enforcePermission(
- android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
+ mContext.enforcePermission(systemUiPermission, callingPid, callingUid,
"DisplayPolicy");
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 90b9a0452eb1..6b7ba66ca9c9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1524,7 +1524,7 @@ public class WindowManagerService extends IWindowManager.Stub
final boolean isRoundedCornerOverlay = (attrs.privateFlags
& PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,
- appOp);
+ appOp, displayId);
if (res != ADD_OKAY) {
return res;
}
@@ -10121,6 +10121,23 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ /**
+ * Returns whether the given UID is the owner of a virtual device, which the given display
+ * belongs to.
+ */
+ @Override
+ public boolean isCallerVirtualDeviceOwner(int displayId, int callingUid) {
+ if (!android.companion.virtualdevice.flags.Flags.statusBarAndInsets()) {
+ return false;
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mAtmService.mTaskSupervisor.isDeviceOwnerUid(displayId, callingUid);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
@RequiresPermission(ACCESS_SURFACE_FLINGER)
@Override
public boolean replaceContentOnDisplay(int displayId, SurfaceControl sc) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index fc619677bb56..2b7e9f902ccf 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -11858,7 +11858,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
setBackwardsCompatibleAppRestrictions(
caller, packageName, restrictions, caller.getUserHandle());
- } else if (Flags.dmrhSetAppRestrictions()) {
+ } else {
final boolean isRoleHolder;
if (who != null) {
// DO or PO
@@ -11905,15 +11905,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
caller.getUserHandle());
});
}
- } else {
- Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
- || (caller.hasPackage() && isCallerDelegate(caller,
- DELEGATION_APP_RESTRICTIONS)));
- mInjector.binderWithCleanCallingIdentity(() -> {
- mUserManager.setApplicationRestrictions(packageName, restrictions,
- caller.getUserHandle());
- });
}
DevicePolicyEventLogger
@@ -13244,7 +13235,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return Bundle.EMPTY;
}
return policies.get(enforcingAdmin).getValue();
- } else if (Flags.dmrhSetAppRestrictions()) {
+ } else {
final boolean isRoleHolder;
if (who != null) {
// Caller is DO or PO. They cannot call this on parent
@@ -13287,19 +13278,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return bundle != null ? bundle : Bundle.EMPTY;
});
}
-
- } else {
- Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
- || (caller.hasPackage() && isCallerDelegate(caller,
- DELEGATION_APP_RESTRICTIONS)));
- return mInjector.binderWithCleanCallingIdentity(() -> {
- Bundle bundle = mUserManager.getApplicationRestrictions(packageName,
- caller.getUserHandle());
- // if no restrictions were saved, mUserManager.getApplicationRestrictions
- // returns null, but DPM method should return an empty Bundle as per JavaDoc
- return bundle != null ? bundle : Bundle.EMPTY;
- });
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
index 30c384a03883..5e868a3089f6 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
@@ -38,7 +38,7 @@ public class DisplayOffloadSessionImplTest {
private DisplayManagerInternal.DisplayOffloader mDisplayOffloader;
@Mock
- private DisplayPowerControllerInterface mDisplayPowerController;
+ private DisplayPowerController mDisplayPowerController;
private DisplayOffloadSessionImpl mSession;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index a7e0ebdd6de1..120cc84193cd 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -118,7 +118,7 @@ public class LocalDisplayAdapterTest {
@Mock
private DisplayManagerFlags mFlags;
@Mock
- private DisplayPowerControllerInterface mMockedDisplayPowerController;
+ private DisplayPowerController mMockedDisplayPowerController;
private Handler mHandler;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
index 8753b251ac98..019ccf93fa11 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
@@ -48,6 +48,7 @@ import org.mockito.MockitoAnnotations
import java.util.LinkedList
import java.util.Queue
import android.util.ArraySet
+import android.view.InputDevice
/**
* Tests for {@link MouseKeysInterceptor}
@@ -68,6 +69,8 @@ class MouseKeysInterceptorTest {
}
private lateinit var mouseKeysInterceptor: MouseKeysInterceptor
+ private lateinit var inputDevice: InputDevice
+
private val clock = OffsettableClock()
private val testLooper = TestLooper { clock.now() }
private val nextInterceptor = TrackingInterceptor()
@@ -98,6 +101,10 @@ class MouseKeysInterceptorTest {
testSession = InputManagerGlobal.createTestSession(iInputManager)
mockInputManager = InputManager(context)
+ inputDevice = createInputDevice(DEVICE_ID)
+ Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID))
+ .thenReturn(inputDevice)
+
Mockito.`when`(mockVirtualDeviceManagerInternal.getDeviceIdsForUid(Mockito.anyInt()))
.thenReturn(ArraySet(setOf(DEVICE_ID)))
LocalServices.removeServiceForTest(VirtualDeviceManagerInternal::class.java)
@@ -115,7 +122,8 @@ class MouseKeysInterceptorTest {
Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
Mockito.`when`(mockAms.traceManager).thenReturn(mockTraceManager)
- mouseKeysInterceptor = MouseKeysInterceptor(mockAms, testLooper.looper, DISPLAY_ID)
+ mouseKeysInterceptor = MouseKeysInterceptor(mockAms, mockInputManager,
+ testLooper.looper, DISPLAY_ID)
mouseKeysInterceptor.next = nextInterceptor
}
@@ -281,6 +289,17 @@ class MouseKeysInterceptorTest {
}
}
+ private fun createInputDevice(
+ deviceId: Int,
+ generation: Int = -1
+ ): InputDevice =
+ InputDevice.Builder()
+ .setId(deviceId)
+ .setName("Device $deviceId")
+ .setDescriptor("descriptor $deviceId")
+ .setGeneration(generation)
+ .build()
+
private class TrackingInterceptor : BaseEventStreamTransformation() {
val events: Queue<KeyEvent> = LinkedList()
diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
index 07934ea90b7e..e694c0b4afc1 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
@@ -188,7 +188,7 @@ public class PhoneWindowManagerTests {
.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
int[] outAppOp = new int[1];
assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_WALLPAPER,
- /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp));
+ /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp, DEFAULT_DISPLAY));
assertThat(outAppOp[0]).isEqualTo(AppOpsManager.OP_NONE);
}
@@ -198,7 +198,7 @@ public class PhoneWindowManagerTests {
.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
int[] outAppOp = new int[1];
assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_ACCESSIBILITY_OVERLAY,
- /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp));
+ /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp, DEFAULT_DISPLAY));
assertThat(outAppOp[0]).isEqualTo(AppOpsManager.OP_CREATE_ACCESSIBILITY_OVERLAY);
}
@@ -208,7 +208,7 @@ public class PhoneWindowManagerTests {
.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
int[] outAppOp = new int[1];
assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_ACCESSIBILITY_OVERLAY,
- /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp));
+ /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp, DEFAULT_DISPLAY));
assertThat(outAppOp[0]).isEqualTo(AppOpsManager.OP_NONE);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index d62c626f9a90..eebb487d16cd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -60,7 +60,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy {
@Override
public int checkAddPermission(int type, boolean isRoundedCornerOverlay, String packageName,
- int[] outAppOp) {
+ int[] outAppOp, int displayId) {
return 0;
}
diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
new file mode 100644
index 000000000000..ad0ef1b3a37f
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input.debug;
+
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.testing.TestableContext;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.input.MotionEventBuilder;
+import com.android.cts.input.PointerBuilder;
+
+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;
+
+/**
+ * Build/Install/Run:
+ * atest TouchpadDebugViewTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class TouchpadDebugViewTest {
+ private static final int TOUCHPAD_DEVICE_ID = 6;
+
+ private TouchpadDebugView mTouchpadDebugView;
+ private WindowManager.LayoutParams mWindowLayoutParams;
+
+ @Mock
+ WindowManager mWindowManager;
+
+ Rect mWindowBounds;
+ WindowMetrics mWindowMetrics;
+ TestableContext mTestableContext;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ mTestableContext = new TestableContext(context);
+
+ mTestableContext.addMockSystemService(WindowManager.class, mWindowManager);
+
+ mWindowBounds = new Rect(0, 0, 2560, 1600);
+ mWindowMetrics = new WindowMetrics(mWindowBounds, new WindowInsets(mWindowBounds), 1.0f);
+
+ when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+
+ mTouchpadDebugView = new TouchpadDebugView(mTestableContext, TOUCHPAD_DEVICE_ID);
+
+ mTouchpadDebugView.measure(
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ );
+
+ doAnswer(invocation -> {
+ mTouchpadDebugView.layout(0, 0, mTouchpadDebugView.getMeasuredWidth(),
+ mTouchpadDebugView.getMeasuredHeight());
+ return null;
+ }).when(mWindowManager).addView(any(), any());
+
+ doAnswer(invocation -> {
+ mTouchpadDebugView.layout(0, 0, mTouchpadDebugView.getMeasuredWidth(),
+ mTouchpadDebugView.getMeasuredHeight());
+ return null;
+ }).when(mWindowManager).updateViewLayout(any(), any());
+
+ mWindowLayoutParams = mTouchpadDebugView.getWindowLayoutParams();
+ mWindowLayoutParams.x = 20;
+ mWindowLayoutParams.y = 20;
+
+ mTouchpadDebugView.layout(0, 0, mTouchpadDebugView.getMeasuredWidth(),
+ mTouchpadDebugView.getMeasuredHeight());
+ }
+
+ @Test
+ public void testDragView() {
+ // Initial view position relative to screen.
+ int initialX = mWindowLayoutParams.x;
+ int initialY = mWindowLayoutParams.y;
+
+ float offsetX = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() + 10;
+ float offsetY = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() + 10;
+
+ // Simulate ACTION_DOWN event (initial touch).
+ MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f)
+ .y(40f)
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionDown);
+
+ verify(mWindowManager, times(0)).updateViewLayout(any(), any());
+
+ // Simulate ACTION_MOVE event (dragging to the right).
+ MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f + offsetX)
+ .y(40f + offsetY)
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionMove);
+
+ ArgumentCaptor<WindowManager.LayoutParams> mWindowLayoutParamsCaptor =
+ ArgumentCaptor.forClass(WindowManager.LayoutParams.class);
+ verify(mWindowManager).updateViewLayout(any(), mWindowLayoutParamsCaptor.capture());
+
+ // Verify position after ACTION_MOVE
+ assertEquals(initialX + (long) offsetX, mWindowLayoutParamsCaptor.getValue().x);
+ assertEquals(initialY + (long) offsetY, mWindowLayoutParamsCaptor.getValue().y);
+
+ // Simulate ACTION_UP event (release touch).
+ MotionEvent actionUp = new MotionEventBuilder(MotionEvent.ACTION_UP, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f + offsetX)
+ .y(40f + offsetY)
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionUp);
+
+ assertEquals(initialX + (long) offsetX, mWindowLayoutParamsCaptor.getValue().x);
+ assertEquals(initialY + (long) offsetY, mWindowLayoutParamsCaptor.getValue().y);
+ }
+
+ @Test
+ public void testDragViewOutOfBounds() {
+ int initialX = mWindowLayoutParams.x;
+ int initialY = mWindowLayoutParams.y;
+
+ MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(initialX + 10f)
+ .y(initialY + 10f)
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionDown);
+
+ verify(mWindowManager, times(0)).updateViewLayout(any(), any());
+
+ // Simulate ACTION_MOVE event (dragging far to the right and bottom, beyond screen bounds)
+ MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(mWindowBounds.width() + mTouchpadDebugView.getWidth())
+ .y(mWindowBounds.height() + mTouchpadDebugView.getHeight())
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionMove);
+
+ ArgumentCaptor<WindowManager.LayoutParams> mWindowLayoutParamsCaptor =
+ ArgumentCaptor.forClass(WindowManager.LayoutParams.class);
+ verify(mWindowManager).updateViewLayout(any(), mWindowLayoutParamsCaptor.capture());
+
+ // Verify the view has been clamped to the right and bottom edges of the screen
+ assertEquals(mWindowBounds.width() - mTouchpadDebugView.getWidth(),
+ mWindowLayoutParamsCaptor.getValue().x);
+ assertEquals(mWindowBounds.height() - mTouchpadDebugView.getHeight(),
+ mWindowLayoutParamsCaptor.getValue().y);
+
+ MotionEvent actionUp = new MotionEventBuilder(MotionEvent.ACTION_UP, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(mWindowBounds.width() + mTouchpadDebugView.getWidth())
+ .y(mWindowBounds.height() + mTouchpadDebugView.getHeight())
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionUp);
+
+ // Verify the view has been clamped to the right and bottom edges of the screen
+ assertEquals(mWindowBounds.width() - mTouchpadDebugView.getWidth(),
+ mWindowLayoutParamsCaptor.getValue().x);
+ assertEquals(mWindowBounds.height() - mTouchpadDebugView.getHeight(),
+ mWindowLayoutParamsCaptor.getValue().y);
+ }
+
+ @Test
+ public void testSlopOffset() {
+ int initialX = mWindowLayoutParams.x;
+ int initialY = mWindowLayoutParams.y;
+
+ float offsetX = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() / 2.0f;
+ float offsetY = -(ViewConfiguration.get(mTestableContext).getScaledTouchSlop() / 2.0f);
+
+ MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(initialX)
+ .y(initialY)
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionDown);
+
+ MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(initialX + offsetX)
+ .y(initialY + offsetY)
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionMove);
+
+ MotionEvent actionUp = new MotionEventBuilder(MotionEvent.ACTION_UP, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(initialX)
+ .y(initialY)
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionUp);
+
+ // In this case the updateViewLayout() method wouldn't be called because the drag
+ // distance hasn't exceeded the slop
+ verify(mWindowManager, times(0)).updateViewLayout(any(), any());
+ }
+
+ @Test
+ public void testViewReturnsToInitialPositionOnCancel() {
+ int initialX = mWindowLayoutParams.x;
+ int initialY = mWindowLayoutParams.y;
+
+ float offsetX = 50;
+ float offsetY = 50;
+
+ MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(initialX)
+ .y(initialY)
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionDown);
+
+ MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(initialX + offsetX)
+ .y(initialY + offsetY)
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionMove);
+
+ ArgumentCaptor<WindowManager.LayoutParams> mWindowLayoutParamsCaptor =
+ ArgumentCaptor.forClass(WindowManager.LayoutParams.class);
+ verify(mWindowManager).updateViewLayout(any(), mWindowLayoutParamsCaptor.capture());
+
+ assertEquals(initialX + (long) offsetX, mWindowLayoutParamsCaptor.getValue().x);
+ assertEquals(initialY + (long) offsetY, mWindowLayoutParamsCaptor.getValue().y);
+
+ // Simulate ACTION_CANCEL event (canceling the touch event stream)
+ MotionEvent actionCancel = new MotionEventBuilder(MotionEvent.ACTION_CANCEL,
+ SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(initialX + offsetX)
+ .y(initialY + offsetY)
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionCancel);
+
+ // Verify the view returns to its initial position
+ verify(mWindowManager, times(2)).updateViewLayout(any(),
+ mWindowLayoutParamsCaptor.capture());
+ assertEquals(initialX, mWindowLayoutParamsCaptor.getValue().x);
+ assertEquals(initialY, mWindowLayoutParamsCaptor.getValue().y);
+ }
+}