Merge "Flexiglass: include alternate bouncer in status bar state calculation" into main
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index e5389b4..11c5b51 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -75,3 +75,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enforce_quota_policy_to_fgs_jobs"
+ namespace: "backstage_power"
+ description: "Applies the normal quota policy to FGS jobs"
+ bug: "341201311"
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index a1c72fb..03a3a0d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -99,10 +99,10 @@
* the number of jobs or sessions that can run within the window. Regardless of bucket, apps will
* not be allowed to run more than 20 jobs within the past 10 minutes.
*
- * Jobs are throttled while an app is not in a foreground state. All jobs are allowed to run
- * freely when an app enters the foreground state and are restricted when the app leaves the
- * foreground state. However, jobs that are started while the app is in the TOP state do not count
- * towards any quota and are not restricted regardless of the app's state change.
+ * Jobs are throttled while an app is not in a TOP or BOUND_TOP state. All jobs are allowed to run
+ * freely when an app enters the TOP or BOUND_TOP state and are restricted when the app leaves those
+ * states. However, jobs that are started while the app is in the TOP state do not count towards any
+ * quota and are not restricted regardless of the app's state change.
*
* Jobs will not be throttled when the device is charging. The device is considered to be charging
* once the {@link BatteryManager#ACTION_CHARGING} intent has been broadcast.
@@ -567,6 +567,11 @@
ActivityManager.getService().registerUidObserver(new QcUidObserver(),
ActivityManager.UID_OBSERVER_PROCSTATE,
ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, null);
+ if (Flags.enforceQuotaPolicyToFgsJobs()) {
+ ActivityManager.getService().registerUidObserver(new QcUidObserver(),
+ ActivityManager.UID_OBSERVER_PROCSTATE,
+ ActivityManager.PROCESS_STATE_BOUND_TOP, null);
+ }
ActivityManager.getService().registerUidObserver(new QcUidObserver(),
ActivityManager.UID_OBSERVER_PROCSTATE,
ActivityManager.PROCESS_STATE_TOP, null);
@@ -2706,6 +2711,12 @@
}
}
+ @VisibleForTesting
+ int getProcessStateQuotaFreeThreshold() {
+ return Flags.enforceQuotaPolicyToFgsJobs() ? ActivityManager.PROCESS_STATE_BOUND_TOP :
+ ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+ }
+
private class QcHandler extends Handler {
QcHandler(Looper looper) {
@@ -2832,15 +2843,15 @@
mTopAppCache.put(uid, true);
mTopAppGraceCache.delete(uid);
if (mForegroundUids.get(uid)) {
- // Went from FGS to TOP. We don't need to reprocess timers or
- // jobs.
+ // Went from a process state with quota free to TOP. We don't
+ // need to reprocess timers or jobs.
break;
}
mForegroundUids.put(uid, true);
isQuotaFree = true;
} else {
final boolean reprocess;
- if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ if (procState <= getProcessStateQuotaFreeThreshold()) {
reprocess = !mForegroundUids.get(uid);
mForegroundUids.put(uid, true);
isQuotaFree = true;
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index dcf82bf..ff0a3dd 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -47,13 +47,6 @@
}
flag {
- name: "start_user_before_scheduled_alarms"
- namespace: "multiuser"
- description: "Persist list of users with alarms scheduled and wakeup stopped users before alarms are due"
- bug: "314907186"
-}
-
-flag {
name: "add_ui_for_sounds_from_background_users"
namespace: "multiuser"
description: "Allow foreground user to dismiss sounds that are coming from background users"
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 6c1aa90..75ffcc3 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -461,6 +461,16 @@
public abstract void stylusGestureStarted(long eventTime);
/**
+ * Called by {@link com.android.server.wm.ContentRecorder} to verify whether
+ * the display is allowed to mirror primary display's content.
+ * @param displayId the id of the display where we mirror to.
+ * @return true if the mirroring dialog is confirmed (display is enabled), or
+ * {@link com.android.server.display.ExternalDisplayPolicy#ENABLE_ON_CONNECT}
+ * system property is enabled.
+ */
+ public abstract boolean isDisplayReadyForMirroring(int displayId);
+
+ /**
* Describes the requested power state of the display.
*
* This object is intended to describe the general characteristics of the
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 177ee6f..061b585 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -24,6 +24,7 @@
import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
+import static com.android.hardware.input.Flags.mouseReverseVerticalScrolling;
import static com.android.hardware.input.Flags.touchpadTapDragging;
import static com.android.hardware.input.Flags.touchpadVisualizer;
import static com.android.input.flags.Flags.enableInputFilterRustImpl;
@@ -363,6 +364,14 @@
}
/**
+ * Returns true if the feature flag for mouse reverse vertical scrolling is enabled.
+ * @hide
+ */
+ public static boolean isMouseReverseVerticalScrollingFeatureFlagEnabled() {
+ return mouseReverseVerticalScrolling();
+ }
+
+ /**
* Returns true if the touchpad visualizer is allowed to appear.
*
* @param context The application context.
@@ -501,6 +510,45 @@
}
/**
+ * Whether mouse vertical scrolling is enabled, this applies only to connected mice.
+ *
+ * @param context The application context.
+ * @return Whether the mouse will have its vertical scrolling reversed
+ * (scroll down to move up).
+ *
+ * @hide
+ */
+ public static boolean isMouseReverseVerticalScrollingEnabled(@NonNull Context context) {
+ if (!isMouseReverseVerticalScrollingFeatureFlagEnabled()) {
+ return false;
+ }
+
+ return Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING, 0, UserHandle.USER_CURRENT)
+ != 0;
+ }
+
+ /**
+ * Sets whether the connected mouse will have its vertical scrolling reversed.
+ *
+ * @param context The application context.
+ * @param reverseScrolling Whether reverse scrolling is enabled.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+ public static void setMouseReverseVerticalScrolling(@NonNull Context context,
+ boolean reverseScrolling) {
+ if (!isMouseReverseVerticalScrollingFeatureFlagEnabled()) {
+ return;
+ }
+
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING, reverseScrolling ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+
+ /**
* Whether Accessibility bounce keys feature is enabled.
*
* <p>
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 3ae9511..f1964e7 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -6368,8 +6368,12 @@
Settings.Global.DEVICE_DEMO_MODE, 0) > 0;
}
- /** @hide */
- public static final void invalidateUserSerialNumberCache() {
+
+ /**
+ * This method is used to invalidate caches, when user was added or removed.
+ * @hide
+ */
+ public static final void invalidateCacheOnUserListChange() {
UserManagerCache.invalidateUserSerialNumber();
}
@@ -6382,7 +6386,7 @@
* @hide
*/
@UnsupportedAppUsage
- @CachedProperty(modsFlagOnOrNone = {})
+ @CachedProperty(modsFlagOnOrNone = {}, api = "user_manager_users")
public int getUserSerialNumber(@UserIdInt int userId) {
// Read only flag should is to fix early access to this API
// cacheUserSerialNumber to be removed after the
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index a1bfe39..81987907 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -232,3 +232,19 @@
bug: "361329788"
is_exported: true
}
+
+flag {
+ name: "enable_angle_allow_list"
+ namespace: "gpu"
+ description: "Whether to read from angle allowlist to determine if app should use ANGLE"
+ is_fixed_read_only: true
+ bug: "370845648"
+}
+
+flag {
+ name: "api_for_backported_fixes"
+ namespace: "media_reliability"
+ description: "Public API app developers use to check if a known issue is fixed on a device."
+ bug: "308461809"
+ is_exported: true
+}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 271970b..1d8fcec 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -261,7 +261,7 @@
is_fixed_read_only: true
namespace: "permissions"
description: "If proc state is decreasing over the restriction threshold and capability is changed, delay if no new capabilities are added"
- bug: "308573169"
+ bug: "347891382"
metadata {
purpose: PURPOSE_BUGFIX
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1a15d09..46bb8e2 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2351,6 +2351,11 @@
/**
* Activity Action: Show the permission screen for allowing apps to post promoted notifications.
+ * Properly formatted priority notifications are elevated in appearance. For example they may be
+ * able to use colors, have richer progress bars, show as chips in the status bar, and/or
+ * permanently appear on always-on-displays. This functionality is intended to be reserved for
+ * user initiated ongoing activities like navigation, phone calls, and ride sharing.
+ *
* <p>
* Input: {@link #EXTRA_APP_PACKAGE}, the package to display.
* <p>
@@ -6205,6 +6210,15 @@
public static final String TOUCHPAD_RIGHT_CLICK_ZONE = "touchpad_right_click_zone";
/**
+ * Whether to enable reversed vertical scrolling for connected mice.
+ *
+ * When enabled, scrolling down on the mouse wheel will move the screen up and vice versa.
+ * @hide
+ */
+ public static final String MOUSE_REVERSE_VERTICAL_SCROLLING =
+ "mouse_reverse_vertical_scrolling";
+
+ /**
* Pointer fill style, specified by
* {@link android.view.PointerIcon.PointerIconVectorStyleFill} constants.
*
@@ -6442,6 +6456,7 @@
PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION);
PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION_COLOR);
PRIVATE_SETTINGS.add(DEFAULT_DEVICE_FONT_SCALE);
+ PRIVATE_SETTINGS.add(MOUSE_REVERSE_VERTICAL_SCROLLING);
}
/**
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 6fb82af..8e35843e 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -65,7 +65,9 @@
ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS(
Flags::enableDesktopWindowingTaskbarRunningApps, true),
ENABLE_DESKTOP_WINDOWING_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false),
- ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS(Flags::enableDesktopWindowingExitTransitions, false);
+ ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS(Flags::enableDesktopWindowingExitTransitions, false),
+ ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS(
+ Flags::enableWindowingTransitionHandlersObservers, false);
private static final String TAG = "DesktopModeFlagsUtil";
// Function called to obtain aconfig flag value.
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 31bb3a6..155494f 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -228,7 +228,7 @@
name: "enable_desktop_windowing_app_handle_education"
namespace: "lse_desktop_experience"
description: "Enables desktop windowing app handle education"
- bug: "348208342"
+ bug: "316006079"
}
flag {
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index e795e809..3865ae4 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -220,6 +220,14 @@
}
optional Touchpad touchpad = 36;
+ message Mouse {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+ optional SettingProto reverse_vertical_scrolling = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+
+ optional Mouse mouse = 38;
+
optional SettingProto tty_mode = 31 [ (android.privacy).dest = DEST_AUTOMATIC ];
message Vibrate {
@@ -277,5 +285,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 38;
+ // Next tag = 39;
}
diff --git a/core/tests/coretests/src/android/app/ActivityManagerTest.java b/core/tests/coretests/src/android/app/ActivityManagerTest.java
index d850f86..85ff846 100644
--- a/core/tests/coretests/src/android/app/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/ActivityManagerTest.java
@@ -60,7 +60,6 @@
public void testProcState() throws Exception {
// For the moment mostly want to confirm we don't crash
assertNotNull(ActivityManager.procStateToString(PROCESS_STATE_SERVICE));
- assertNotNull(ActivityManager.processStateAmToProto(PROCESS_STATE_SERVICE));
assertTrue(ActivityManager.isProcStateBackground(PROCESS_STATE_SERVICE));
assertFalse(ActivityManager.isProcStateCached(PROCESS_STATE_SERVICE));
assertFalse(ActivityManager.isForegroundService(PROCESS_STATE_SERVICE));
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
index b972882..cd52421 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -111,12 +111,6 @@
assertEquals(config.reqKeyboardType, vconfig.keyboard);
assertEquals(config.reqTouchScreen, vconfig.touchscreen);
assertEquals(config.reqNavigation, vconfig.navigation);
- if (vconfig.navigation == Configuration.NAVIGATION_NONAV) {
- assertNotNull(config.reqInputFeatures & ConfigurationInfo.INPUT_FEATURE_FIVE_WAY_NAV);
- }
- if (vconfig.keyboard != Configuration.KEYBOARD_UNDEFINED) {
- assertNotNull(config.reqInputFeatures & ConfigurationInfo.INPUT_FEATURE_HARD_KEYBOARD);
- }
}
@SmallTest
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index 5a277316f..379e052 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -156,6 +156,21 @@
)
}
+ fun logTaskInfoStateInit() {
+ logTaskUpdate(
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INIT_STATSD,
+ /* session_id */ 0,
+ TaskUpdate(
+ visibleTaskCount = 0,
+ instanceId = 0,
+ uid = 0,
+ taskHeight = 0,
+ taskWidth = 0,
+ taskX = 0,
+ taskY = 0)
+ )
+ }
+
private fun logTaskUpdate(taskEvent: Int, sessionId: Int, taskUpdate: TaskUpdate) {
FrameworkStatsLog.write(
DESKTOP_MODE_TASK_UPDATE_ATOM_ID,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index b8507e3..f847aa89 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -102,6 +102,7 @@
SystemProperties.set(
VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY,
VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY_DEFAULT_VALUE)
+ desktopModeEventLogger.logTaskInfoStateInit()
}
override fun onTransitionReady(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index d7a132d..dde9fda 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.desktopmode
-import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
@@ -397,6 +396,37 @@
}
}
+ @Test
+ fun logTaskInfoStateInit_logsTaskInfoChangedStateInit() {
+ desktopModeEventLogger.logTaskInfoStateInit()
+ verify {
+ FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ /* task_event */
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INIT_STATSD),
+ /* instance_id */
+ eq(0),
+ /* uid */
+ eq(0),
+ /* task_height */
+ eq(0),
+ /* task_width */
+ eq(0),
+ /* task_x */
+ eq(0),
+ /* task_y */
+ eq(0),
+ /* session_id */
+ eq(0),
+ /* minimize_reason */
+ eq(UNSET_MINIMIZE_REASON),
+ /* unminimize_reason */
+ eq(UNSET_UNMINIMIZE_REASON),
+ /* visible_task_count */
+ eq(0)
+ )
+ }
+ }
+
private companion object {
private const val SESSION_ID = 1
private const val TASK_ID = 1
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index daf7e7d..e7593b5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -115,6 +115,9 @@
val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java)
verify(mockShellInit).addInitCallback(initRunnableCaptor.capture(), same(transitionObserver))
initRunnableCaptor.value.run()
+ // verify this initialisation interaction to leave the desktopmodeEventLogger mock in a
+ // consistent state with no outstanding interactions when test cases start executing.
+ verify(desktopModeEventLogger).logTaskInfoStateInit()
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
index 5293011..d8b6707 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
@@ -207,20 +207,6 @@
}
@Test
- public void getHotspotIconResource_deviceTypeExists_shouldNotNull() {
- assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_PHONE))
- .isNotNull();
- assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_TABLET))
- .isNotNull();
- assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_LAPTOP))
- .isNotNull();
- assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_WATCH))
- .isNotNull();
- assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_AUTO))
- .isNotNull();
- }
-
- @Test
public void testInternetIconInjector_getIcon_returnsCorrectValues() {
WifiUtils.InternetIconInjector iconInjector = new WifiUtils.InternetIconInjector(mContext);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 2cdd0ae..e1b967b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -106,6 +106,7 @@
Settings.System.UNREAD_NOTIFICATION_DOT_INDICATOR,
Settings.System.AUTO_LAUNCH_MEDIA_CONTROLS,
Settings.System.LOCALE_PREFERENCES,
+ Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING,
Settings.System.TOUCHPAD_POINTER_SPEED,
Settings.System.TOUCHPAD_NATURAL_SCROLLING,
Settings.System.TOUCHPAD_TAP_TO_CLICK,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 2823277..1f51e1a 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -221,6 +221,7 @@
POINTER_ICON_VECTOR_STYLE_STROKE_END));
VALIDATORS.put(System.POINTER_SCALE,
new InclusiveFloatRangeValidator(DEFAULT_POINTER_SCALE, LARGE_POINTER_SCALE));
+ VALIDATORS.put(System.MOUSE_REVERSE_VERTICAL_SCROLLING, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.TOUCHPAD_POINTER_SPEED, new InclusiveIntegerRangeValidator(-7, 7));
VALIDATORS.put(System.TOUCHPAD_NATURAL_SCROLLING, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.TOUCHPAD_TAP_TO_CLICK, BOOLEAN_VALIDATOR);
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 07a1e63..380344a 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -148,5 +148,10 @@
{
"name": "SystemUIGoogleRobo2RNGTests"
}
+ ],
+ "imports": [
+ {
+ "path": "cts/tests/tests/multiuser"
+ }
]
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
index ba9fa92..cd18925 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -34,10 +35,12 @@
private var gestureState: GestureState = NotStarted
private val gestureMonitor =
- BackGestureMonitor(
- gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
- gestureStateChangedCallback = { gestureState = it },
- )
+ BackGestureMonitor(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt())
+
+ @Before
+ fun before() {
+ gestureMonitor.addGestureStateCallback { gestureState = it }
+ }
@Test
fun triggersGestureFinishedForThreeFingerGestureRight() {
@@ -82,7 +85,7 @@
}
private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) {
- events.forEach { gestureMonitor.processTouchpadEvent(it) }
+ events.forEach { gestureMonitor.accept(it) }
assertThat(gestureState).isEqualTo(expectedState)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt
index a83ed56..3f1633a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt
@@ -36,10 +36,7 @@
private var triggered = false
private val handler =
TouchpadGestureHandler(
- BackGestureMonitor(
- gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
- gestureStateChangedCallback = {},
- ),
+ BackGestureMonitor(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt()),
EasterEggGestureMonitor(callback = { triggered = true }),
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt
index 59cc026..edf0e56 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -34,10 +35,12 @@
private var gestureState: GestureState = GestureState.NotStarted
private val gestureMonitor =
- HomeGestureMonitor(
- gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
- gestureStateChangedCallback = { gestureState = it },
- )
+ HomeGestureMonitor(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt())
+
+ @Before
+ fun before() {
+ gestureMonitor.addGestureStateCallback { gestureState = it }
+ }
@Test
fun triggersGestureFinishedForThreeFingerGestureUp() {
@@ -78,7 +81,7 @@
}
private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) {
- events.forEach { gestureMonitor.processTouchpadEvent(it) }
+ events.forEach { gestureMonitor.accept(it) }
assertThat(gestureState).isEqualTo(expectedState)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt
index 7eac6bb..f68e773 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
@@ -44,7 +45,7 @@
}
private var gestureState: GestureState = GestureState.NotStarted
- private val velocityTracker =
+ private val velocityTracker1D =
mock<VelocityTracker1D> {
// by default return correct speed for the gesture - as if pointer is slowing down
on { calculateVelocity() } doReturn SLOW
@@ -52,11 +53,15 @@
private val gestureMonitor =
RecentAppsGestureMonitor(
gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
- gestureStateChangedCallback = { gestureState = it },
velocityThresholdPxPerMs = THRESHOLD_VELOCITY_PX_PER_MS,
- velocityTracker = velocityTracker,
+ velocityTracker = VerticalVelocityTracker(velocityTracker1D),
)
+ @Before
+ fun before() {
+ gestureMonitor.addGestureStateCallback { gestureState = it }
+ }
+
@Test
fun triggersGestureFinishedForThreeFingerGestureUp() {
assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = Finished)
@@ -64,7 +69,7 @@
@Test
fun doesntTriggerGestureFinished_onGestureSpeedTooHigh() {
- whenever(velocityTracker.calculateVelocity()).thenReturn(FAST)
+ whenever(velocityTracker1D.calculateVelocity()).thenReturn(FAST)
assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = NotStarted)
}
@@ -102,7 +107,7 @@
}
private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) {
- events.forEach { gestureMonitor.processTouchpadEvent(it) }
+ events.forEach { gestureMonitor.accept(it) }
assertThat(gestureState).isEqualTo(expectedState)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
index 4d26366..9f7ea679 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,14 +36,14 @@
class TouchpadGestureHandlerTest : SysuiTestCase() {
private var gestureState: GestureState = GestureState.NotStarted
- private val handler =
- TouchpadGestureHandler(
- BackGestureMonitor(
- gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
- gestureStateChangedCallback = { gestureState = it },
- ),
- EasterEggGestureMonitor {},
- )
+ private val gestureMonitor =
+ BackGestureMonitor(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt())
+ private val handler = TouchpadGestureHandler(gestureMonitor, EasterEggGestureMonitor {})
+
+ @Before
+ fun before() {
+ gestureMonitor.addGestureStateCallback { gestureState = it }
+ }
@Test
fun handlesEventsFromTouchpad() {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
index b1736b1..c09509d 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
@@ -14,7 +14,6 @@
package com.android.systemui.plugins;
-import android.annotation.IntegerRes;
import android.content.ComponentName;
import android.media.AudioManager;
import android.media.AudioSystem;
@@ -22,6 +21,8 @@
import android.os.VibrationEffect;
import android.util.SparseArray;
+import androidx.annotation.StringRes;
+
import com.android.systemui.plugins.VolumeDialogController.Callbacks;
import com.android.systemui.plugins.VolumeDialogController.State;
import com.android.systemui.plugins.VolumeDialogController.StreamState;
@@ -90,7 +91,7 @@
public int levelMax;
public boolean muted;
public boolean muteSupported;
- public @IntegerRes int name;
+ public @StringRes int name;
public String remoteLabel;
public boolean routedToBluetooth;
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
index b56ed8c..589dbf9 100644
--- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
@@ -24,6 +24,8 @@
import com.android.systemui.display.data.repository.DisplayRepositoryImpl
import com.android.systemui.display.data.repository.DisplayScopeRepository
import com.android.systemui.display.data.repository.DisplayScopeRepositoryImpl
+import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
+import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepositoryImpl
import com.android.systemui.display.data.repository.FocusedDisplayRepository
import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
@@ -58,6 +60,11 @@
@Binds fun displayScopeRepository(impl: DisplayScopeRepositoryImpl): DisplayScopeRepository
+ @Binds
+ fun displayWindowPropertiesRepository(
+ impl: DisplayWindowPropertiesRepositoryImpl
+ ): DisplayWindowPropertiesRepository
+
companion object {
@Provides
@SysUISingleton
@@ -72,5 +79,19 @@
CoreStartable.NOP
}
}
+
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(DisplayWindowPropertiesRepository::class)
+ fun displayWindowPropertiesRepoAsCoreStartable(
+ repoLazy: Lazy<DisplayWindowPropertiesRepositoryImpl>
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ return repoLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt
new file mode 100644
index 0000000..88d3a28
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.display.data.repository
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.view.Display
+import android.view.WindowManager
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.shared.model.DisplayWindowProperties
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.google.common.collect.HashBasedTable
+import com.google.common.collect.Table
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/** Provides per display instances of [DisplayWindowProperties]. */
+interface DisplayWindowPropertiesRepository {
+
+ /**
+ * Returns a [DisplayWindowProperties] instance for a given display id and window type.
+ *
+ * @throws IllegalArgumentException if no display with the given display id exists.
+ */
+ fun get(
+ displayId: Int,
+ @WindowManager.LayoutParams.WindowType windowType: Int,
+ ): DisplayWindowProperties
+}
+
+@SysUISingleton
+class DisplayWindowPropertiesRepositoryImpl
+@Inject
+constructor(
+ @Background private val backgroundApplicationScope: CoroutineScope,
+ private val globalContext: Context,
+ private val globalWindowManager: WindowManager,
+ private val displayRepository: DisplayRepository,
+) : DisplayWindowPropertiesRepository, CoreStartable {
+
+ init {
+ StatusBarConnectedDisplays.assertInNewMode()
+ }
+
+ private val properties: Table<Int, Int, DisplayWindowProperties> = HashBasedTable.create()
+
+ override fun get(
+ displayId: Int,
+ @WindowManager.LayoutParams.WindowType windowType: Int,
+ ): DisplayWindowProperties {
+ val display =
+ displayRepository.getDisplay(displayId)
+ ?: throw IllegalArgumentException("Display with id $displayId doesn't exist")
+ return properties.get(displayId, windowType)
+ ?: create(display, windowType).also { properties.put(displayId, windowType, it) }
+ }
+
+ override fun start() {
+ backgroundApplicationScope.launch(
+ CoroutineName("DisplayWindowPropertiesRepositoryImpl#start")
+ ) {
+ displayRepository.displayRemovalEvent.collect { removedDisplayId ->
+ properties.row(removedDisplayId).clear()
+ }
+ }
+ }
+
+ private fun create(display: Display, windowType: Int): DisplayWindowProperties {
+ val displayId = display.displayId
+ return if (displayId == Display.DEFAULT_DISPLAY) {
+ // For the default display, we can just reuse the global/application properties.
+ // Creating a window context is expensive, therefore we avoid it.
+ DisplayWindowProperties(
+ displayId = displayId,
+ windowType = windowType,
+ context = globalContext,
+ windowManager = globalWindowManager,
+ )
+ } else {
+ val context = createWindowContext(display, windowType)
+ @SuppressLint("NonInjectedService") // Need to manually get the service
+ val windowManager = context.getSystemService(WindowManager::class.java) as WindowManager
+ DisplayWindowProperties(displayId, windowType, context, windowManager)
+ }
+ }
+
+ private fun createWindowContext(display: Display, windowType: Int): Context =
+ globalContext.createWindowContext(display, windowType, /* options= */ null).also {
+ it.setTheme(R.style.Theme_SystemUI)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.write("perDisplayContexts: $properties")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/shared/model/DisplayWindowProperties.kt b/packages/SystemUI/src/com/android/systemui/display/shared/model/DisplayWindowProperties.kt
new file mode 100644
index 0000000..6acc296
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/shared/model/DisplayWindowProperties.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.display.shared.model
+
+import android.content.Context
+import android.view.WindowManager
+
+/** Represents a display specific group of window related properties. */
+data class DisplayWindowProperties(
+ /** The id of the display associated with this instance. */
+ val displayId: Int,
+ /**
+ * The window type that was used to create the [Context] in this instance, using
+ * [Context.createWindowContext]. This is the window type that can be used when adding views to
+ * the [WindowManager] associated with this instance.
+ */
+ @WindowManager.LayoutParams.WindowType val windowType: Int,
+ /**
+ * The display specific [Context] created using [Context.createWindowContext] with window type
+ * associated with this instance.
+ */
+ val context: Context,
+
+ /**
+ * The display specific [WindowManager] instance to be used when adding windows of the type
+ * associated with this instance.
+ */
+ val windowManager: WindowManager,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index cf238d5..cd1642e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -22,15 +22,20 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.StatusBarDataLayerModule
import com.android.systemui.statusbar.phone.LightBarController
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl
+import com.android.systemui.statusbar.window.MultiDisplayStatusBarWindowControllerStore
+import com.android.systemui.statusbar.window.SingleDisplayStatusBarWindowControllerStore
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.statusbar.window.StatusBarWindowControllerImpl
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import dagger.Binds
+import dagger.Lazy
import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
@@ -62,13 +67,19 @@
@ClassKey(StatusBarSignalPolicy::class)
abstract fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable
+ @Binds
+ @SysUISingleton
+ abstract fun statusBarWindowControllerFactory(
+ implFactory: StatusBarWindowControllerImpl.Factory
+ ): StatusBarWindowController.Factory
+
companion object {
@Provides
@SysUISingleton
- fun statusBarWindowController(
- context: Context?,
- viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager?,
+ fun defaultStatusBarWindowController(
+ context: Context,
+ viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
factory: StatusBarWindowControllerImpl.Factory,
): StatusBarWindowController {
return factory.create(context, viewCaptureAwareWindowManager)
@@ -76,6 +87,33 @@
@Provides
@SysUISingleton
+ fun windowControllerStore(
+ multiDisplayImplLazy: Lazy<MultiDisplayStatusBarWindowControllerStore>,
+ singleDisplayImplLazy: Lazy<SingleDisplayStatusBarWindowControllerStore>,
+ ): StatusBarWindowControllerStore {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ multiDisplayImplLazy.get()
+ } else {
+ singleDisplayImplLazy.get()
+ }
+ }
+
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(MultiDisplayStatusBarWindowControllerStore::class)
+ fun multiDisplayControllerStoreAsCoreStartable(
+ storeLazy: Lazy<MultiDisplayStatusBarWindowControllerStore>
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ storeLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
+
+ @Provides
+ @SysUISingleton
@OngoingCallLog
fun provideOngoingCallLogBuffer(factory: LogBufferFactory): LogBuffer {
return factory.create("OngoingCall", 75)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
index f5cfc8c..e0bf00f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
@@ -26,6 +26,7 @@
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.vcn.VcnTransportInfo
+import android.net.vcn.VcnUtils
import android.net.wifi.WifiInfo
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.annotation.ArrayRes
@@ -161,7 +162,9 @@
defaultNetworkCapabilities
.map { networkCapabilities ->
networkCapabilities?.run {
- val subId = (transportInfo as? VcnTransportInfo)?.subId
+ val subId =
+ VcnUtils.getSubIdFromVcnCaps(connectivityManager, networkCapabilities)
+
// Never return an INVALID_SUBSCRIPTION_ID (-1)
if (subId != INVALID_SUBSCRIPTION_ID) {
subId
@@ -245,9 +248,9 @@
* info.
*/
fun NetworkCapabilities.getMainOrUnderlyingWifiInfo(
- connectivityManager: ConnectivityManager,
+ connectivityManager: ConnectivityManager
): WifiInfo? {
- val mainWifiInfo = this.getMainWifiInfo()
+ val mainWifiInfo = this.getMainWifiInfo(connectivityManager)
if (mainWifiInfo != null) {
return mainWifiInfo
}
@@ -264,7 +267,9 @@
// eventually traced to a wifi or carrier merged connection. So, check those underlying
// networks for possible wifi information as well. See b/225902574.
return this.underlyingNetworks?.firstNotNullOfOrNull { underlyingNetwork ->
- connectivityManager.getNetworkCapabilities(underlyingNetwork)?.getMainWifiInfo()
+ connectivityManager
+ .getNetworkCapabilities(underlyingNetwork)
+ ?.getMainWifiInfo(connectivityManager)
}
}
@@ -272,7 +277,9 @@
* Checks the network capabilities for wifi info, but does *not* check the underlying
* networks. See [getMainOrUnderlyingWifiInfo].
*/
- private fun NetworkCapabilities.getMainWifiInfo(): WifiInfo? {
+ private fun NetworkCapabilities.getMainWifiInfo(
+ connectivityManager: ConnectivityManager
+ ): WifiInfo? {
// Wifi info can either come from a WIFI Transport, or from a CELLULAR transport for
// virtual networks like VCN.
val canHaveWifiInfo =
@@ -286,7 +293,7 @@
// [com.android.settingslib.Utils.tryGetWifiInfoForVcn]. It's copied instead of
// re-used because it makes the logic here clearer, and because the method will be
// removed once this pipeline is fully launched.
- is VcnTransportInfo -> currentTransportInfo.wifiInfo
+ is VcnTransportInfo -> VcnUtils.getWifiInfoFromVcnCaps(connectivityManager, this)
is WifiInfo -> currentTransportInfo
else -> null
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
index 421e5c4..e8dc934 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
@@ -16,8 +16,10 @@
package com.android.systemui.statusbar.window
+import android.content.Context
import android.view.View
import android.view.ViewGroup
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.fragments.FragmentHostManager
import java.util.Optional
@@ -73,4 +75,11 @@
* this#setForceStatusBarVisible} together and use some sort of ranking system instead.
*/
fun setOngoingProcessRequiresStatusBarVisible(visible: Boolean)
+
+ interface Factory {
+ fun create(
+ context: Context,
+ viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+ ): StatusBarWindowController
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
index 1ee7cf3..d709e5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
@@ -354,11 +354,13 @@
}
@AssistedFactory
- public interface Factory {
+ public interface Factory extends StatusBarWindowController.Factory {
/** Creates a new instance. */
+ @NonNull
+ @Override
StatusBarWindowControllerImpl create(
- Context context,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager);
+ @NonNull Context context,
+ @NonNull ViewCaptureAwareWindowManager viewCaptureAwareWindowManager);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
new file mode 100644
index 0000000..5f30b37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.statusbar.window
+
+import android.view.Display
+import android.view.WindowManager
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/** Store that allows to retrieve per display instances of [StatusBarWindowController]. */
+interface StatusBarWindowControllerStore {
+ /**
+ * The instance for the default/main display of the device. For example, on a phone or a tablet,
+ * the default display is the internal/built-in display of the device.
+ *
+ * Note that the id of the default display is [Display.DEFAULT_DISPLAY].
+ */
+ val defaultDisplay: StatusBarWindowController
+
+ /**
+ * Returns an instance for a specific display id.
+ *
+ * @throws IllegalArgumentException if [displayId] doesn't match the id of any existing
+ * displays.
+ */
+ fun forDisplay(displayId: Int): StatusBarWindowController
+}
+
+@SysUISingleton
+class MultiDisplayStatusBarWindowControllerStore
+@Inject
+constructor(
+ @Background private val backgroundApplicationScope: CoroutineScope,
+ private val controllerFactory: StatusBarWindowController.Factory,
+ private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
+ private val viewCaptureAwareWindowManagerFactory: ViewCaptureAwareWindowManager.Factory,
+ private val displayRepository: DisplayRepository,
+) : StatusBarWindowControllerStore, CoreStartable {
+
+ init {
+ StatusBarConnectedDisplays.assertInNewMode()
+ }
+
+ private val perDisplayControllers = ConcurrentHashMap<Int, StatusBarWindowController>()
+
+ override fun start() {
+ backgroundApplicationScope.launch(CoroutineName("StatusBarWindowController#start")) {
+ displayRepository.displayRemovalEvent.collect { displayId ->
+ perDisplayControllers.remove(displayId)
+ }
+ }
+ }
+
+ override val defaultDisplay: StatusBarWindowController
+ get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+ override fun forDisplay(displayId: Int): StatusBarWindowController {
+ if (displayRepository.getDisplay(displayId) == null) {
+ throw IllegalArgumentException("Display with id $displayId doesn't exist.")
+ }
+ return perDisplayControllers.computeIfAbsent(displayId) {
+ createControllerForDisplay(displayId)
+ }
+ }
+
+ private fun createControllerForDisplay(displayId: Int): StatusBarWindowController {
+ val statusBarDisplayContext =
+ displayWindowPropertiesRepository.get(
+ displayId = displayId,
+ windowType = WindowManager.LayoutParams.TYPE_STATUS_BAR,
+ )
+ val viewCaptureAwareWindowManager =
+ viewCaptureAwareWindowManagerFactory.create(statusBarDisplayContext.windowManager)
+ return controllerFactory.create(
+ statusBarDisplayContext.context,
+ viewCaptureAwareWindowManager,
+ )
+ }
+}
+
+@SysUISingleton
+class SingleDisplayStatusBarWindowControllerStore
+@Inject
+constructor(private val controller: StatusBarWindowController) : StatusBarWindowControllerStore {
+
+ init {
+ StatusBarConnectedDisplays.assertInLegacyMode()
+ }
+
+ override val defaultDisplay = controller
+
+ override fun forDisplay(displayId: Int) = controller
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index bfc5429..6879a34 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
@@ -27,10 +27,7 @@
import com.android.systemui.touchpad.tutorial.ui.gesture.BackGestureMonitor
@Composable
-fun BackGestureTutorialScreen(
- onDoneButtonClicked: () -> Unit,
- onBack: () -> Unit,
-) {
+fun BackGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
val screenConfig =
TutorialScreenConfig(
colors = rememberScreenColors(),
@@ -39,18 +36,20 @@
titleResId = R.string.touchpad_back_gesture_action_title,
bodyResId = R.string.touchpad_back_gesture_guidance,
titleSuccessResId = R.string.touchpad_back_gesture_success_title,
- bodySuccessResId = R.string.touchpad_back_gesture_success_body
+ bodySuccessResId = R.string.touchpad_back_gesture_success_body,
),
animations =
TutorialScreenConfig.Animations(
educationResId = R.raw.trackpad_back_edu,
- successResId = R.raw.trackpad_back_success
- )
+ successResId = R.raw.trackpad_back_success,
+ ),
)
val gestureMonitorProvider =
DistanceBasedGestureMonitorProvider(
monitorFactory = { distanceThresholdPx, gestureStateCallback ->
- BackGestureMonitor(distanceThresholdPx, gestureStateCallback)
+ BackGestureMonitor(distanceThresholdPx).also {
+ it.addGestureStateCallback(gestureStateCallback)
+ }
}
)
GestureTutorialScreen(screenConfig, gestureMonitorProvider, onDoneButtonClicked, onBack)
@@ -67,7 +66,7 @@
rememberColorFilterProperty(".tertiaryFixedDim", tertiaryFixedDim),
rememberColorFilterProperty(".onTertiaryFixed", onTertiaryFixed),
rememberColorFilterProperty(".onTertiary", onTertiary),
- rememberColorFilterProperty(".onTertiaryFixedVariant", onTertiaryFixedVariant)
+ rememberColorFilterProperty(".onTertiaryFixedVariant", onTertiaryFixedVariant),
)
val screenColors =
remember(dynamicProperties) {
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
index f2fec5f..a55fa44 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
@@ -26,10 +26,7 @@
import com.android.systemui.touchpad.tutorial.ui.gesture.HomeGestureMonitor
@Composable
-fun HomeGestureTutorialScreen(
- onDoneButtonClicked: () -> Unit,
- onBack: () -> Unit,
-) {
+fun HomeGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
val screenConfig =
TutorialScreenConfig(
colors = rememberScreenColors(),
@@ -38,18 +35,20 @@
titleResId = R.string.touchpad_home_gesture_action_title,
bodyResId = R.string.touchpad_home_gesture_guidance,
titleSuccessResId = R.string.touchpad_home_gesture_success_title,
- bodySuccessResId = R.string.touchpad_home_gesture_success_body
+ bodySuccessResId = R.string.touchpad_home_gesture_success_body,
),
animations =
TutorialScreenConfig.Animations(
educationResId = R.raw.trackpad_home_edu,
- successResId = R.raw.trackpad_home_success
- )
+ successResId = R.raw.trackpad_home_success,
+ ),
)
val gestureMonitorProvider =
DistanceBasedGestureMonitorProvider(
monitorFactory = { distanceThresholdPx, gestureStateCallback ->
- HomeGestureMonitor(distanceThresholdPx, gestureStateCallback)
+ HomeGestureMonitor(distanceThresholdPx).also {
+ it.addGestureStateCallback(gestureStateCallback)
+ }
}
)
GestureTutorialScreen(screenConfig, gestureMonitorProvider, onDoneButtonClicked, onBack)
@@ -64,7 +63,7 @@
rememberLottieDynamicProperties(
rememberColorFilterProperty(".primaryFixedDim", primaryFixedDim),
rememberColorFilterProperty(".onPrimaryFixed", onPrimaryFixed),
- rememberColorFilterProperty(".onPrimaryFixedVariant", onPrimaryFixedVariant)
+ rememberColorFilterProperty(".onPrimaryFixedVariant", onPrimaryFixedVariant),
)
val screenColors =
remember(dynamicProperties) {
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
index b2fb6cd..6ee15aa 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
@@ -29,10 +29,7 @@
import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureMonitor
@Composable
-fun RecentAppsGestureTutorialScreen(
- onDoneButtonClicked: () -> Unit,
- onBack: () -> Unit,
-) {
+fun RecentAppsGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
val screenConfig =
TutorialScreenConfig(
colors = rememberScreenColors(),
@@ -41,20 +38,20 @@
titleResId = R.string.touchpad_recent_apps_gesture_action_title,
bodyResId = R.string.touchpad_recent_apps_gesture_guidance,
titleSuccessResId = R.string.touchpad_recent_apps_gesture_success_title,
- bodySuccessResId = R.string.touchpad_recent_apps_gesture_success_body
+ bodySuccessResId = R.string.touchpad_recent_apps_gesture_success_body,
),
animations =
TutorialScreenConfig.Animations(
educationResId = R.raw.trackpad_recent_apps_edu,
- successResId = R.raw.trackpad_recent_apps_success
- )
+ successResId = R.raw.trackpad_recent_apps_success,
+ ),
)
val gestureMonitorProvider =
object : GestureMonitorProvider {
@Composable
override fun rememberGestureMonitor(
resources: Resources,
- gestureStateChangedCallback: (GestureState) -> Unit
+ gestureStateChangedCallback: (GestureState) -> Unit,
): TouchpadGestureMonitor {
val distanceThresholdPx =
resources.getDimensionPixelSize(
@@ -63,11 +60,9 @@
val velocityThresholdPxPerMs =
resources.getDimension(R.dimen.touchpad_recent_apps_gesture_velocity_threshold)
return remember(distanceThresholdPx, velocityThresholdPxPerMs) {
- RecentAppsGestureMonitor(
- distanceThresholdPx,
- gestureStateChangedCallback,
- velocityThresholdPxPerMs
- )
+ RecentAppsGestureMonitor(distanceThresholdPx, velocityThresholdPxPerMs).also {
+ it.addGestureStateCallback(gestureStateChangedCallback)
+ }
}
}
}
@@ -83,7 +78,7 @@
rememberLottieDynamicProperties(
rememberColorFilterProperty(".secondaryFixedDim", secondaryFixedDim),
rememberColorFilterProperty(".onSecondaryFixed", onSecondaryFixed),
- rememberColorFilterProperty(".onSecondaryFixedVariant", onSecondaryFixedVariant)
+ rememberColorFilterProperty(".onSecondaryFixedVariant", onSecondaryFixedVariant),
)
val screenColors =
remember(dynamicProperties) {
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
index ecb5574..490f04d 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
@@ -20,18 +20,21 @@
import kotlin.math.abs
/** Monitors for touchpad back gesture, that is three fingers swiping left or right */
-class BackGestureMonitor(
- private val gestureDistanceThresholdPx: Int,
- override val gestureStateChangedCallback: (GestureState) -> Unit,
-) : TouchpadGestureMonitor {
- private val distanceTracker = DistanceTracker()
+class BackGestureMonitor(private val gestureDistanceThresholdPx: Int) : TouchpadGestureMonitor {
- override fun processTouchpadEvent(event: MotionEvent) {
+ private val distanceTracker = DistanceTracker()
+ private var gestureStateChangedCallback: (GestureState) -> Unit = {}
+
+ override fun addGestureStateCallback(callback: (GestureState) -> Unit) {
+ gestureStateChangedCallback = callback
+ }
+
+ override fun accept(event: MotionEvent) {
if (!isThreeFingerTouchpadSwipe(event)) return
- val distanceState = distanceTracker.processEvent(event)
- updateGestureStateBasedOnDistance(
+ val gestureState = distanceTracker.processEvent(event)
+ updateGestureState(
gestureStateChangedCallback,
- distanceState,
+ gestureState,
isFinished = { abs(it.deltaX) >= gestureDistanceThresholdPx },
progress = { 0f },
)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/DistanceTracker.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/DistanceTracker.kt
index 70d9366..d482358 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/DistanceTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/DistanceTracker.kt
@@ -38,10 +38,10 @@
}
}
-sealed interface DistanceGestureState
+sealed class DistanceGestureState(val deltaX: Float, val deltaY: Float)
-class Started(val deltaX: Float, val deltaY: Float) : DistanceGestureState
+class Started(deltaX: Float, deltaY: Float) : DistanceGestureState(deltaX, deltaY)
-class Moving(val deltaX: Float, val deltaY: Float) : DistanceGestureState
+class Moving(deltaX: Float, deltaY: Float) : DistanceGestureState(deltaX, deltaY)
-class Finished(val deltaX: Float, val deltaY: Float) : DistanceGestureState
+class Finished(deltaX: Float, deltaY: Float) : DistanceGestureState(deltaX, deltaY)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt
index c1caeb3..f194677 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt
@@ -16,11 +16,8 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
-/**
- * Helper function for gesture recognizers to have common state triggering logic based on distance
- * only.
- */
-inline fun updateGestureStateBasedOnDistance(
+/** Helper function for gesture recognizers to have common state triggering logic */
+inline fun updateGestureState(
gestureStateChangedCallback: (GestureState) -> Unit,
gestureState: DistanceGestureState?,
isFinished: (Finished) -> Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt
index fdcf9de..83d4f56 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt
@@ -19,18 +19,21 @@
import android.view.MotionEvent
/** Monitors for touchpad home gesture, that is three fingers swiping up */
-class HomeGestureMonitor(
- private val gestureDistanceThresholdPx: Int,
- override val gestureStateChangedCallback: (GestureState) -> Unit,
-) : TouchpadGestureMonitor {
- private val distanceTracker = DistanceTracker()
+class HomeGestureMonitor(private val gestureDistanceThresholdPx: Int) : TouchpadGestureMonitor {
- override fun processTouchpadEvent(event: MotionEvent) {
+ private val distanceTracker = DistanceTracker()
+ private var gestureStateChangedCallback: (GestureState) -> Unit = {}
+
+ override fun addGestureStateCallback(callback: (GestureState) -> Unit) {
+ gestureStateChangedCallback = callback
+ }
+
+ override fun accept(event: MotionEvent) {
if (!isThreeFingerTouchpadSwipe(event)) return
- val distanceState = distanceTracker.processEvent(event)
- updateGestureStateBasedOnDistance(
+ val gestureState = distanceTracker.processEvent(event)
+ updateGestureState(
gestureStateChangedCallback,
- distanceState,
+ gestureState,
isFinished = { -it.deltaY >= gestureDistanceThresholdPx },
progress = { 0f },
)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt
index dd31ce3..1731bb8 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt
@@ -17,7 +17,6 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
import android.view.MotionEvent
-import androidx.compose.ui.input.pointer.util.VelocityTracker1D
import kotlin.math.abs
/**
@@ -27,45 +26,30 @@
*/
class RecentAppsGestureMonitor(
private val gestureDistanceThresholdPx: Int,
- override val gestureStateChangedCallback: (GestureState) -> Unit,
private val velocityThresholdPxPerMs: Float,
- private val velocityTracker: VelocityTracker1D = VelocityTracker1D(isDataDifferential = false),
+ private val distanceTracker: DistanceTracker = DistanceTracker(),
+ private val velocityTracker: VerticalVelocityTracker = VerticalVelocityTracker(),
) : TouchpadGestureMonitor {
- private var xStart = 0f
- private var yStart = 0f
+ private var gestureStateChangedCallback: (GestureState) -> Unit = {}
- override fun processTouchpadEvent(event: MotionEvent) {
- val action = event.actionMasked
- velocityTracker.addDataPoint(event.eventTime, event.y)
- when (action) {
- MotionEvent.ACTION_DOWN -> {
- if (isThreeFingerTouchpadSwipe(event)) {
- xStart = event.x
- yStart = event.y
- gestureStateChangedCallback(GestureState.InProgress())
- }
- }
- MotionEvent.ACTION_UP -> {
- if (isThreeFingerTouchpadSwipe(event) && isRecentAppsGesture(event)) {
- gestureStateChangedCallback(GestureState.Finished)
- } else {
- gestureStateChangedCallback(GestureState.NotStarted)
- }
- velocityTracker.resetTracking()
- }
- MotionEvent.ACTION_CANCEL -> {
- velocityTracker.resetTracking()
- }
- }
+ override fun addGestureStateCallback(callback: (GestureState) -> Unit) {
+ gestureStateChangedCallback = callback
}
- private fun isRecentAppsGesture(event: MotionEvent): Boolean {
- // below is trying to mirror behavior of TriggerSwipeUpTouchTracker#onGestureEnd.
- // We're diving velocity by 1000, to have the same unit of measure: pixels/ms.
- val swipeDistance = yStart - event.y
- val velocity = velocityTracker.calculateVelocity() / 1000
- return swipeDistance >= gestureDistanceThresholdPx &&
- abs(velocity) <= velocityThresholdPxPerMs
+ override fun accept(event: MotionEvent) {
+ if (!isThreeFingerTouchpadSwipe(event)) return
+ val gestureState = distanceTracker.processEvent(event)
+ velocityTracker.accept(event)
+
+ updateGestureState(
+ gestureStateChangedCallback,
+ gestureState,
+ isFinished = { state ->
+ -state.deltaY >= gestureDistanceThresholdPx &&
+ abs(velocityTracker.calculateVelocity().value) <= velocityThresholdPxPerMs
+ },
+ progress = { 0f },
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
index 88671d4..4b82ba1 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
@@ -18,13 +18,14 @@
import android.view.InputDevice
import android.view.MotionEvent
+import java.util.function.Consumer
/**
* Allows listening to touchpadGesture and calling onDone when gesture was triggered. Can have all
* motion events passed to [onMotionEvent] and will filter touchpad events accordingly
*/
class TouchpadGestureHandler(
- private val gestureMonitor: TouchpadGestureMonitor,
+ private val gestureMonitor: Consumer<MotionEvent>,
private val easterEggGestureMonitor: EasterEggGestureMonitor,
) {
@@ -40,7 +41,7 @@
if (isTwoFingerSwipe(event)) {
easterEggGestureMonitor.processTouchpadEvent(event)
} else {
- gestureMonitor.processTouchpadEvent(event)
+ gestureMonitor.accept(event)
}
true
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt
index 4655c98..9216821 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt
@@ -17,15 +17,11 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
import android.view.MotionEvent
+import java.util.function.Consumer
-/**
- * Monitor for touchpad gestures that calls [gestureStateChangedCallback] when [GestureState]
- * changes. All tracked motion events should be passed to [processTouchpadEvent]
- */
-interface TouchpadGestureMonitor {
- val gestureStateChangedCallback: (GestureState) -> Unit
-
- fun processTouchpadEvent(event: MotionEvent)
+/** Monitor for touchpad gestures that can notify callback when [GestureState] changes. */
+interface TouchpadGestureMonitor : Consumer<MotionEvent> {
+ fun addGestureStateCallback(callback: (GestureState) -> Unit)
}
fun isThreeFingerTouchpadSwipe(event: MotionEvent) = isNFingerTouchpadSwipe(event, fingerCount = 3)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/VelocityTracker.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/VelocityTracker.kt
new file mode 100644
index 0000000..9b38eca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/VelocityTracker.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.touchpad.tutorial.ui.gesture
+
+import android.view.MotionEvent
+import androidx.compose.ui.input.pointer.util.VelocityTracker1D
+import java.util.function.Consumer
+
+/** Velocity in pixels/ms. */
+@JvmInline value class Velocity(val value: Float)
+
+/**
+ * Tracks velocity for processed MotionEvents. Useful for recognizing gestures based on velocity.
+ */
+interface VelocityTracker : Consumer<MotionEvent> {
+
+ fun calculateVelocity(): Velocity
+}
+
+class VerticalVelocityTracker(
+ private val velocityTracker: VelocityTracker1D = VelocityTracker1D(isDataDifferential = false)
+) : VelocityTracker {
+
+ override fun accept(event: MotionEvent) {
+ val action = event.actionMasked
+ if (action == MotionEvent.ACTION_DOWN) {
+ velocityTracker.resetTracking()
+ }
+ velocityTracker.addDataPoint(event.eventTime, event.y)
+ }
+
+ /**
+ * Calculates velocity on demand - this calculation can be expensive so shouldn't be called
+ * after every event.
+ */
+ override fun calculateVelocity() = Velocity(velocityTracker.calculateVelocity() / 1000)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 1f92bc1..bbd8f3dc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -59,7 +59,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Observer;
import com.android.internal.annotations.GuardedBy;
@@ -110,8 +109,8 @@
// It is safe to use 99 as the broadcast stream now. There are only 10+ default audio
// streams defined in AudioSystem for now and audio team is in the middle of restructure,
// no new default stream is preferred.
- @VisibleForTesting static final int DYNAMIC_STREAM_BROADCAST = 99;
- private static final int DYNAMIC_STREAM_REMOTE_START_INDEX = 100;
+ public static final int DYNAMIC_STREAM_BROADCAST = 99;
+ public static final int DYNAMIC_STREAM_REMOTE_START_INDEX = 100;
private static final AudioAttributes SONIFICIATION_VIBRATION_ATTRIBUTES =
new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt
index f1443e3..500cc0b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt
@@ -23,7 +23,7 @@
/** Models a state of the Volume Dialog. */
data class VolumeDialogStateModel(
- val states: Map<Int, VolumeDialogStreamStateModel>,
+ val states: Map<Int, VolumeDialogStreamModel>,
val ringerModeInternal: Int = 0,
val ringerModeExternal: Int = 0,
val zenMode: Int = 0,
@@ -39,7 +39,7 @@
constructor(
legacyState: VolumeDialogController.State
) : this(
- states = legacyState.states.mapToMap { VolumeDialogStreamStateModel(it) },
+ states = legacyState.states.mapToMap { VolumeDialogStreamModel(it) },
ringerModeInternal = legacyState.ringerModeInternal,
ringerModeExternal = legacyState.ringerModeExternal,
zenMode = legacyState.zenMode,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamModel.kt
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamStateModel.kt
rename to packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamModel.kt
index a9d367d..26c96ea 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamStateModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamModel.kt
@@ -16,18 +16,18 @@
package com.android.systemui.volume.dialog.domain.model
-import android.annotation.IntegerRes
+import androidx.annotation.StringRes
import com.android.systemui.plugins.VolumeDialogController
/** Models a state of an audio stream of the Volume Dialog. */
-data class VolumeDialogStreamStateModel(
+data class VolumeDialogStreamModel(
val isDynamic: Boolean = false,
val level: Int = 0,
val levelMin: Int = 0,
val levelMax: Int = 0,
val muted: Boolean = false,
val muteSupported: Boolean = false,
- @IntegerRes val name: Int = 0,
+ @StringRes val name: Int = 0,
val remoteLabel: String? = null,
val routedToBluetooth: Boolean = false,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
new file mode 100644
index 0000000..81507ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.volume.dialog.sliders.domain.interactor
+
+import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogStreamModel
+import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.mapNotNull
+
+/** Operates a state of particular slider of the Volume Dialog. */
+class VolumeDialogSliderInteractor
+@AssistedInject
+constructor(
+ @Assisted private val sliderType: VolumeDialogSliderType,
+ volumeDialogStateInteractor: VolumeDialogStateInteractor,
+ private val volumeDialogController: VolumeDialogController,
+) {
+
+ val slider: Flow<VolumeDialogStreamModel> =
+ volumeDialogStateInteractor.volumeDialogState.mapNotNull {
+ it.states[sliderType.audioStream]
+ }
+
+ fun setStreamVolume(userLevel: Int) {
+ volumeDialogController.setStreamVolume(sliderType.audioStream, userLevel)
+ }
+
+ @VolumeDialogScope
+ @AssistedFactory
+ interface Factory {
+
+ fun create(sliderType: VolumeDialogSliderType): VolumeDialogSliderInteractor
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt
new file mode 100644
index 0000000..325e4c95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.volume.dialog.sliders.domain.interactor
+
+import com.android.systemui.volume.VolumeDialogControllerImpl
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
+import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
+import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSlidersModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** Provides a state for the Sliders section of the Volume Dialog. */
+@VolumeDialogScope
+class VolumeDialogSlidersInteractor
+@Inject
+constructor(volumeDialogStateInteractor: VolumeDialogStateInteractor) {
+
+ val sliders: Flow<VolumeDialogSlidersModel> =
+ volumeDialogStateInteractor.volumeDialogState.map {
+ val sliderTypes: List<VolumeDialogSliderType> =
+ it.states.keys.sortedWith(StreamsSorter).map { audioStream ->
+ when {
+ audioStream == VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST ->
+ VolumeDialogSliderType.AudioSharingStream(audioStream)
+ audioStream >=
+ VolumeDialogControllerImpl.DYNAMIC_STREAM_REMOTE_START_INDEX ->
+ VolumeDialogSliderType.RemoteMediaStream(audioStream)
+ else -> VolumeDialogSliderType.Stream(audioStream)
+ }
+ }
+ VolumeDialogSlidersModel(
+ slider = sliderTypes.first(),
+ floatingSliders = sliderTypes.drop(1),
+ )
+ }
+
+ private object StreamsSorter : Comparator<Int> {
+
+ // TODO(b/369992924) order the streams
+ override fun compare(lhs: Int, rhs: Int): Int {
+ return lhs - rhs
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSliderType.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSliderType.kt
new file mode 100644
index 0000000..18a2689
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSliderType.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.volume.dialog.sliders.domain.model
+
+/** Models different possible audio sliders shown in the Volume Dialog. */
+sealed interface VolumeDialogSliderType {
+
+ // VolumeDialogController uses the same model for every slider type. We need to follow the same
+ // logic until we refactor and decouple data and domain layers from the VolumeDialogController
+ // into separated interactors.
+ val audioStream: Int
+
+ class Stream(override val audioStream: Int) : VolumeDialogSliderType
+
+ class RemoteMediaStream(override val audioStream: Int) : VolumeDialogSliderType
+
+ class AudioSharingStream(override val audioStream: Int) : VolumeDialogSliderType
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSlidersModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSlidersModel.kt
new file mode 100644
index 0000000..91a3328
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSlidersModel.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.volume.dialog.sliders.domain.model
+
+/** Models a state of the sliders section of the Volume Dialog. */
+data class VolumeDialogSlidersModel(
+ val slider: VolumeDialogSliderType,
+ val floatingSliders: List<VolumeDialogSliderType>,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
new file mode 100644
index 0000000..25a5f28
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.volume.dialog.sliders.ui
+
+import android.view.View
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.android.systemui.lifecycle.WindowLifecycleState
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.lifecycle.setSnapshotBinding
+import com.android.systemui.lifecycle.viewModel
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
+
+class VolumeDialogSliderViewBinder
+@AssistedInject
+constructor(@Assisted private val viewModelProvider: () -> VolumeDialogSliderViewModel) {
+
+ fun bind(view: View) {
+ with(view) {
+ repeatWhenAttached {
+ viewModel(
+ traceName = "VolumeDialogSliderViewBinder",
+ minWindowLifecycleState = WindowLifecycleState.ATTACHED,
+ factory = { viewModelProvider() },
+ ) { viewModel ->
+ setSnapshotBinding {}
+
+ awaitCancellation()
+ }
+ }
+ }
+ }
+
+ @AssistedFactory
+ @VolumeDialogScope
+ interface Factory {
+
+ fun create(
+ viewModelProvider: () -> VolumeDialogSliderViewModel
+ ): VolumeDialogSliderViewBinder
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
new file mode 100644
index 0000000..0a00f70
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.volume.dialog.sliders.ui
+
+import android.view.View
+import com.android.systemui.lifecycle.WindowLifecycleState
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.lifecycle.setSnapshotBinding
+import com.android.systemui.lifecycle.viewModel
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSlidersViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.awaitCancellation
+
+@VolumeDialogScope
+class VolumeDialogSlidersViewBinder
+@Inject
+constructor(private val viewModelFactory: VolumeDialogSlidersViewModel.Factory) {
+
+ fun bind(view: View) {
+ with(view) {
+ repeatWhenAttached {
+ viewModel(
+ traceName = "VolumeDialogSlidersViewBinder",
+ minWindowLifecycleState = WindowLifecycleState.ATTACHED,
+ factory = { viewModelFactory.create() },
+ ) { viewModel ->
+ setSnapshotBinding {}
+
+ awaitCancellation()
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
new file mode 100644
index 0000000..27b8f2f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.volume.dialog.sliders.ui.viewmodel
+
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogStreamModel
+import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.Flow
+
+class VolumeDialogSliderViewModel
+@AssistedInject
+constructor(@Assisted private val interactor: VolumeDialogSliderInteractor) {
+
+ val model: Flow<VolumeDialogStreamModel> = interactor.slider
+
+ fun setStreamVolume(volume: Int) {
+ interactor.setStreamVolume(volume)
+ }
+
+ @AssistedFactory
+ interface Factory {
+
+ fun create(interactor: VolumeDialogSliderInteractor): VolumeDialogSliderViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
new file mode 100644
index 0000000..b5b292f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.volume.dialog.sliders.ui.viewmodel
+
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
+import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor
+import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
+import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+class VolumeDialogSlidersViewModel
+@AssistedInject
+constructor(
+ @VolumeDialog coroutineScope: CoroutineScope,
+ private val slidersInteractor: VolumeDialogSlidersInteractor,
+ private val sliderInteractorFactory: VolumeDialogSliderInteractor.Factory,
+ private val sliderViewModelFactory: VolumeDialogSliderViewModel.Factory,
+ private val sliderViewBinderFactory: VolumeDialogSliderViewBinder.Factory,
+) {
+
+ val sliders: Flow<VolumeDialogSliderUiModel> =
+ slidersInteractor.sliders
+ .distinctUntilChanged()
+ .map { slidersModel ->
+ VolumeDialogSliderUiModel(
+ sliderViewBinder = createSliderViewBinder(slidersModel.slider),
+ floatingSliderViewBinders =
+ slidersModel.floatingSliders.map(::createSliderViewBinder),
+ )
+ }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+ .filterNotNull()
+
+ private fun createSliderViewBinder(type: VolumeDialogSliderType): VolumeDialogSliderViewBinder =
+ sliderViewBinderFactory.create {
+ sliderViewModelFactory.create(sliderInteractorFactory.create(type))
+ }
+
+ @AssistedFactory
+ interface Factory {
+
+ fun create(): VolumeDialogSlidersViewModel
+ }
+}
+
+/** Models slider ui */
+data class VolumeDialogSliderUiModel(
+ val sliderViewBinder: VolumeDialogSliderViewBinder,
+ val floatingSliderViewBinders: List<VolumeDialogSliderViewBinder>,
+)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
index 85e8ab4..5741d64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
@@ -122,6 +122,7 @@
@Test
@Throws(IOException::class)
+ @DisableFlags(FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE)
fun test_imageClipData_loadFailure() {
whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver)
whenever(mMockContext.resources).thenReturn(mContext.resources)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt
new file mode 100644
index 0000000..ff3186a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.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.display.data.repository
+
+import android.content.testableContext
+import android.platform.test.annotations.EnableFlags
+import android.view.Display
+import android.view.mockWindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.shared.model.DisplayWindowProperties
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.unconfinedTestDispatcher
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class DisplayWindowPropertiesRepositoryImplTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().also { it.testDispatcher = it.unconfinedTestDispatcher }
+ private val fakeDisplayRepository = kosmos.displayRepository
+ private val testScope = kosmos.testScope
+
+ private val applicationContext = kosmos.testableContext
+ private val applicationWindowManager = kosmos.mockWindowManager
+
+ private val repo =
+ DisplayWindowPropertiesRepositoryImpl(
+ kosmos.applicationCoroutineScope,
+ applicationContext,
+ applicationWindowManager,
+ fakeDisplayRepository,
+ )
+
+ @Before
+ fun start() {
+ repo.start()
+ }
+
+ @Before
+ fun addDisplays() = runBlocking {
+ fakeDisplayRepository.addDisplay(createDisplay(DEFAULT_DISPLAY_ID))
+ fakeDisplayRepository.addDisplay(createDisplay(NON_DEFAULT_DISPLAY_ID))
+ }
+
+ @Test
+ fun get_defaultDisplayId_returnsDefaultProperties() =
+ testScope.runTest {
+ val displayContext = repo.get(DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)
+
+ assertThat(displayContext)
+ .isEqualTo(
+ DisplayWindowProperties(
+ displayId = DEFAULT_DISPLAY_ID,
+ windowType = WINDOW_TYPE_FOO,
+ context = applicationContext,
+ windowManager = applicationWindowManager,
+ )
+ )
+ }
+
+ @Test
+ fun get_nonDefaultDisplayId_returnsNewStatusBarContext() =
+ testScope.runTest {
+ val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)
+
+ assertThat(displayContext.context).isNotSameInstanceAs(applicationContext)
+ }
+
+ @Test
+ fun get_nonDefaultDisplayId_returnsNewWindowManager() =
+ testScope.runTest {
+ val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)
+
+ assertThat(displayContext.windowManager).isNotSameInstanceAs(applicationWindowManager)
+ }
+
+ @Test
+ fun get_multipleCallsForDefaultDisplay_returnsSameInstance() =
+ testScope.runTest {
+ val displayContext = repo.get(DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)
+
+ assertThat(repo.get(DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO))
+ .isSameInstanceAs(displayContext)
+ }
+
+ @Test
+ fun get_multipleCallsForNonDefaultDisplay_returnsSameInstance() =
+ testScope.runTest {
+ val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)
+
+ assertThat(repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO))
+ .isSameInstanceAs(displayContext)
+ }
+
+ @Test
+ fun get_multipleCalls_differentType_returnsNewInstance() =
+ testScope.runTest {
+ val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)
+
+ assertThat(repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_BAR))
+ .isNotSameInstanceAs(displayContext)
+ }
+
+ @Test
+ fun get_afterDisplayRemoved_returnsNewInstance() =
+ testScope.runTest {
+ val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)
+
+ fakeDisplayRepository.removeDisplay(NON_DEFAULT_DISPLAY_ID)
+ fakeDisplayRepository.addDisplay(createDisplay(NON_DEFAULT_DISPLAY_ID))
+
+ assertThat(repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO))
+ .isNotSameInstanceAs(displayContext)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun get_nonExistingDisplayId_throws() =
+ testScope.runTest { repo.get(NON_EXISTING_DISPLAY_ID, WINDOW_TYPE_FOO) }
+
+ private fun createDisplay(displayId: Int) =
+ mock<Display> { on { getDisplayId() } doReturn displayId }
+
+ companion object {
+ private const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
+ private const val NON_DEFAULT_DISPLAY_ID = DEFAULT_DISPLAY_ID + 1
+ private const val NON_EXISTING_DISPLAY_ID = DEFAULT_DISPLAY_ID + 2
+ private const val WINDOW_TYPE_FOO = 123
+ private const val WINDOW_TYPE_BAR = 321
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
index 6febb91..7a579ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
@@ -58,7 +58,7 @@
private static final int MIN_RSSI = -100;
private static final int MAX_RSSI = -55;
private WifiInfo mWifiInfo = mock(WifiInfo.class);
- private VcnTransportInfo mVcnTransportInfo = mock(VcnTransportInfo.class);
+ private VcnTransportInfo mVcnTransportInfo = new VcnTransportInfo.Builder().build();
@Before
public void setUp() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 59fc0d1..87cda64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -591,8 +591,8 @@
ArgumentCaptor<FooterView> captor = ArgumentCaptor.forClass(FooterView.class);
verify(mStackScroller).setFooterView(captor.capture());
- assertNotNull(captor.getValue().findViewById(R.id.manage_text).hasOnClickListeners());
- assertNotNull(captor.getValue().findViewById(R.id.dismiss_text).hasOnClickListeners());
+ assertNotNull(captor.getValue().findViewById(R.id.manage_text));
+ assertNotNull(captor.getValue().findViewById(R.id.dismiss_text));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 328d310..c48898a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -136,6 +136,7 @@
private val wifiLogBuffer = LogBuffer("wifi", maxSize = 100, logcatEchoTracker = mock())
private val wifiPickerTrackerCallback =
argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>()
+ private val vcnTransportInfo = VcnTransportInfo.Builder().build()
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -1003,6 +1004,18 @@
assertThat(latest).isTrue()
}
+ private fun newWifiNetwork(wifiInfo: WifiInfo): Network {
+ val network = mock<Network>()
+ val capabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(wifiInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(network)).thenReturn(capabilities)
+
+ return network
+ }
+
/** Regression test for b/272586234. */
@Test
fun hasCarrierMergedConnection_carrierMergedViaWifiWithVcnTransport_isTrue() =
@@ -1012,10 +1025,12 @@
whenever(this.isCarrierMerged).thenReturn(true)
whenever(this.isPrimary).thenReturn(true)
}
+ val underlyingWifi = newWifiNetwork(carrierMergedInfo)
val caps =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
}
val latest by collectLastValue(underTest.hasCarrierMergedConnection)
@@ -1034,10 +1049,12 @@
whenever(this.isCarrierMerged).thenReturn(true)
whenever(this.isPrimary).thenReturn(true)
}
+ val underlyingWifi = newWifiNetwork(carrierMergedInfo)
val caps =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
}
val latest by collectLastValue(underTest.hasCarrierMergedConnection)
@@ -1094,10 +1111,15 @@
whenever(this.isCarrierMerged).thenReturn(true)
whenever(this.isPrimary).thenReturn(true)
}
+
+ // The Wifi network that is under the VCN network
+ val physicalWifiNetwork = newWifiNetwork(carrierMergedInfo)
+
val underlyingCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(physicalWifiNetwork))
}
whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
.thenReturn(underlyingCapabilities)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
index 0945742..88f262b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
@@ -23,6 +23,7 @@
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
import android.net.NetworkCapabilities.TRANSPORT_VPN
import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.TelephonyNetworkSpecifier
import android.net.VpnTransportInfo
import android.net.vcn.VcnTransportInfo
import android.net.wifi.WifiInfo
@@ -74,6 +75,8 @@
private val testScope = kosmos.testScope
private val tunerService = mock<TunerService>()
+ private val vcnTransportInfo = VcnTransportInfo.Builder().build()
+
@Before
fun setUp() {
createAndSetRepo()
@@ -343,6 +346,30 @@
assertThat(latest!!.wifi.isDefault).isTrue()
}
+ private fun newWifiNetwork(wifiInfo: WifiInfo): Network {
+ val network = mock<Network>()
+ val capabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(wifiInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(network)).thenReturn(capabilities)
+
+ return network
+ }
+
+ private fun newCellNetwork(subId: Int): Network {
+ val network = mock<Network>()
+ val capabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.networkSpecifier).thenReturn(TelephonyNetworkSpecifier(subId))
+ }
+ whenever(connectivityManager.getNetworkCapabilities(network)).thenReturn(capabilities)
+
+ return network
+ }
+
@Test
fun defaultConnections_carrierMergedViaWifiWithVcnTransport_wifiAndCarrierMergedDefault() =
testScope.runTest {
@@ -350,10 +377,12 @@
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingWifi = newWifiNetwork(carrierMergedInfo)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(false)
}
@@ -373,10 +402,12 @@
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingWifi = newWifiNetwork(carrierMergedInfo)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(false)
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
}
@@ -561,10 +592,12 @@
val underlyingCarrierMergedNetwork = mock<Network>()
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingWifi = newWifiNetwork(carrierMergedInfo)
val underlyingCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
}
whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
.thenReturn(underlyingCapabilities)
@@ -645,14 +678,15 @@
@Test
fun vcnSubId_tracksVcnTransportInfo() =
testScope.runTest {
- val vcnInfo = VcnTransportInfo(SUB_1_ID)
+ val underlyingCell = newCellNetwork(SUB_1_ID)
val latest by collectLastValue(underTest.vcnSubId)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(vcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingCell))
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
@@ -663,14 +697,15 @@
@Test
fun vcnSubId_filersOutInvalid() =
testScope.runTest {
- val vcnInfo = VcnTransportInfo(INVALID_SUBSCRIPTION_ID)
+ val underlyingCell = newCellNetwork(INVALID_SUBSCRIPTION_ID)
val latest by collectLastValue(underTest.vcnSubId)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(vcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingCell))
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
@@ -703,11 +738,12 @@
val latest by collectLastValue(underTest.vcnSubId)
val wifiInfo = mock<WifiInfo>()
- val vcnInfo = VcnTransportInfo(wifiInfo)
+ val underlyingWifi = newWifiNetwork(wifiInfo)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(vcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
@@ -721,14 +757,15 @@
val latest by collectLastValue(underTest.vcnSubId)
val wifiInfo = mock<WifiInfo>()
- val wifiVcnInfo = VcnTransportInfo(wifiInfo)
- val sub1VcnInfo = VcnTransportInfo(SUB_1_ID)
- val sub2VcnInfo = VcnTransportInfo(SUB_2_ID)
+ val underlyingWifi = newWifiNetwork(wifiInfo)
+ val underlyingCell1 = newCellNetwork(SUB_1_ID)
+ val underlyingCell2 = newCellNetwork(SUB_2_ID)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(wifiVcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
}
// WIFI VCN info
@@ -738,14 +775,16 @@
// Cellular VCN info with subId 1
whenever(capabilities.hasTransport(eq(TRANSPORT_CELLULAR))).thenReturn(true)
- whenever(capabilities.transportInfo).thenReturn(sub1VcnInfo)
+ whenever(capabilities.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(capabilities.underlyingNetworks).thenReturn(listOf(underlyingCell1))
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest).isEqualTo(SUB_1_ID)
// Cellular VCN info with subId 2
- whenever(capabilities.transportInfo).thenReturn(sub2VcnInfo)
+ whenever(capabilities.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(capabilities.underlyingNetworks).thenReturn(listOf(underlyingCell2))
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
@@ -776,11 +815,12 @@
@Test
fun getMainOrUnderlyingWifiInfo_vcnWithWifi_hasInfo() {
val wifiInfo = mock<WifiInfo>()
- val vcnInfo = VcnTransportInfo(wifiInfo)
+ val underlyingWifi = newWifiNetwork(wifiInfo)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(vcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
}
val result = capabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
@@ -860,11 +900,15 @@
fun getMainOrUnderlyingWifiInfo_cellular_underlyingVcnWithWifi_hasInfo() {
val wifiInfo = mock<WifiInfo>()
val underlyingNetwork = mock<Network>()
- val underlyingVcnInfo = VcnTransportInfo(wifiInfo)
+
+ // The Wifi network that is under the VCN network
+ val physicalWifiNetwork = newWifiNetwork(wifiInfo)
+
val underlyingWifiCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(underlyingVcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(physicalWifiNetwork))
}
whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
.thenReturn(underlyingWifiCapabilities)
@@ -887,11 +931,15 @@
@DisableFlags(FLAG_STATUS_BAR_ALWAYS_CHECK_UNDERLYING_NETWORKS)
fun getMainOrUnderlyingWifiInfo_notCellular_underlyingVcnWithWifi_noInfo() {
val underlyingNetwork = mock<Network>()
- val underlyingVcnInfo = VcnTransportInfo(mock<WifiInfo>())
+
+ // The Wifi network that is under the VCN network
+ val physicalWifiNetwork = newWifiNetwork(mock<WifiInfo>())
+
val underlyingWifiCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(underlyingVcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(physicalWifiNetwork))
}
whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
.thenReturn(underlyingWifiCapabilities)
@@ -917,10 +965,15 @@
val underlyingCarrierMergedNetwork = mock<Network>()
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+
+ // The Wifi network that is under the VCN network
+ val physicalWifiNetwork = newWifiNetwork(carrierMergedInfo)
+
val underlyingCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(physicalWifiNetwork))
}
whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
.thenReturn(underlyingCapabilities)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt
new file mode 100644
index 0000000..faaa4c4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.statusbar.window
+
+import android.platform.test.annotations.EnableFlags
+import android.view.Display
+import android.view.WindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.app.viewcapture.mockViewCaptureAwareWindowManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.fakeDisplayWindowPropertiesRepository
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.unconfinedTestDispatcher
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().also { it.testDispatcher = it.unconfinedTestDispatcher }
+ private val testScope = kosmos.testScope
+ private val fakeDisplayRepository = kosmos.displayRepository
+
+ private val store =
+ MultiDisplayStatusBarWindowControllerStore(
+ backgroundApplicationScope = kosmos.applicationCoroutineScope,
+ controllerFactory = kosmos.fakeStatusBarWindowControllerFactory,
+ displayWindowPropertiesRepository = kosmos.fakeDisplayWindowPropertiesRepository,
+ viewCaptureAwareWindowManagerFactory =
+ object : ViewCaptureAwareWindowManager.Factory {
+ override fun create(
+ windowManager: WindowManager
+ ): ViewCaptureAwareWindowManager {
+ return kosmos.mockViewCaptureAwareWindowManager
+ }
+ },
+ displayRepository = fakeDisplayRepository,
+ )
+
+ @Before
+ fun start() {
+ store.start()
+ }
+
+ @Before
+ fun addDisplays() = runBlocking {
+ fakeDisplayRepository.addDisplay(createDisplay(DEFAULT_DISPLAY_ID))
+ fakeDisplayRepository.addDisplay(createDisplay(NON_DEFAULT_DISPLAY_ID))
+ }
+
+ @Test
+ fun forDisplay_defaultDisplay_multipleCalls_returnsSameInstance() =
+ testScope.runTest {
+ val controller = store.defaultDisplay
+
+ assertThat(store.defaultDisplay).isSameInstanceAs(controller)
+ }
+
+ @Test
+ fun forDisplay_nonDefaultDisplay_multipleCalls_returnsSameInstance() =
+ testScope.runTest {
+ val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID)
+
+ assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isSameInstanceAs(controller)
+ }
+
+ @Test
+ fun forDisplay_nonDefaultDisplay_afterDisplayRemoved_returnsNewInstance() =
+ testScope.runTest {
+ val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID)
+
+ fakeDisplayRepository.removeDisplay(NON_DEFAULT_DISPLAY_ID)
+ fakeDisplayRepository.addDisplay(createDisplay(NON_DEFAULT_DISPLAY_ID))
+
+ assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isNotSameInstanceAs(controller)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun forDisplay_nonExistingDisplayId_throws() =
+ testScope.runTest { store.forDisplay(NON_EXISTING_DISPLAY_ID) }
+
+ private fun createDisplay(displayId: Int): Display = mock {
+ on { getDisplayId() } doReturn displayId
+ }
+
+ companion object {
+ private const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
+ private const val NON_DEFAULT_DISPLAY_ID = DEFAULT_DISPLAY_ID + 1
+ private const val NON_EXISTING_DISPLAY_ID = DEFAULT_DISPLAY_ID + 2
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt
new file mode 100644
index 0000000..e1c6699
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.app.viewcapture
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+val Kosmos.mockViewCaptureAwareWindowManager by
+ Kosmos.Fixture { mock<ViewCaptureAwareWindowManager>() }
+
+var Kosmos.viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager by
+ Kosmos.Fixture { mockViewCaptureAwareWindowManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryKosmos.kt
new file mode 100644
index 0000000..ff4ba61
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.display.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+
+val Kosmos.fakeDisplayScopeRepository by
+ Kosmos.Fixture { FakeDisplayScopeRepository(testDispatcher) }
+
+var Kosmos.displayScopeRepository: DisplayScopeRepository by
+ Kosmos.Fixture { fakeDisplayScopeRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryKosmos.kt
new file mode 100644
index 0000000..65b18c1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.display.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeDisplayWindowPropertiesRepository by
+ Kosmos.Fixture { FakeDisplayWindowPropertiesRepository() }
+
+var Kosmos.displayWindowPropertiesRepository: DisplayWindowPropertiesRepository by
+ Kosmos.Fixture { fakeDisplayWindowPropertiesRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayScopeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayScopeRepository.kt
new file mode 100644
index 0000000..3c25924
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayScopeRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.display.data.repository
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+
+class FakeDisplayScopeRepository(private val dispatcher: CoroutineDispatcher) :
+ DisplayScopeRepository {
+
+ private val perDisplayScopes = mutableMapOf<Int, CoroutineScope>()
+
+ override fun scopeForDisplay(displayId: Int): CoroutineScope {
+ return perDisplayScopes.computeIfAbsent(displayId) { CoroutineScope(dispatcher) }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
new file mode 100644
index 0000000..9282f27
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.display.data.repository
+
+import com.android.systemui.display.shared.model.DisplayWindowProperties
+import com.google.common.collect.HashBasedTable
+import org.mockito.kotlin.mock
+
+class FakeDisplayWindowPropertiesRepository : DisplayWindowPropertiesRepository {
+
+ private val properties = HashBasedTable.create<Int, Int, DisplayWindowProperties>()
+
+ override fun get(displayId: Int, windowType: Int): DisplayWindowProperties {
+ return properties.get(displayId, windowType)
+ ?: DisplayWindowProperties(
+ displayId = displayId,
+ windowType = windowType,
+ context = mock(),
+ windowManager = mock(),
+ )
+ .also { properties.put(displayId, windowType, it) }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt
new file mode 100644
index 0000000..10f328b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.statusbar.window
+
+import android.content.Context
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+
+class FakeStatusBarWindowControllerFactory : StatusBarWindowController.Factory {
+ override fun create(
+ context: Context,
+ viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+ ) = FakeStatusBarWindowController()
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerStore.kt
new file mode 100644
index 0000000..d19e322
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerStore.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.statusbar.window
+
+import android.view.Display
+
+class FakeStatusBarWindowControllerStore : StatusBarWindowControllerStore {
+
+ private val perDisplayControllers = mutableMapOf<Int, FakeStatusBarWindowController>()
+
+ override val defaultDisplay
+ get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+ override fun forDisplay(displayId: Int): StatusBarWindowController {
+ return perDisplayControllers.computeIfAbsent(displayId) { FakeStatusBarWindowController() }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
index c198b35..6c6f243 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
@@ -21,3 +21,15 @@
val Kosmos.fakeStatusBarWindowController by Kosmos.Fixture { FakeStatusBarWindowController() }
var Kosmos.statusBarWindowController by Kosmos.Fixture { fakeStatusBarWindowController }
+
+val Kosmos.fakeStatusBarWindowControllerStore by
+ Kosmos.Fixture { FakeStatusBarWindowControllerStore() }
+
+var Kosmos.statusBarWindowControllerStore: StatusBarWindowControllerStore by
+ Kosmos.Fixture { fakeStatusBarWindowControllerStore }
+
+val Kosmos.fakeStatusBarWindowControllerFactory by
+ Kosmos.Fixture { FakeStatusBarWindowControllerFactory() }
+
+var Kosmos.statusBarWindowControllerFactory: StatusBarWindowController.Factory by
+ Kosmos.Fixture { fakeStatusBarWindowControllerFactory }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 2485626..5236b03 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -3629,42 +3629,68 @@
}
@GuardedBy({"mService", "mProcLock"})
- private int updateLruProcessInternalLSP(ProcessRecord app, long now, int index,
- int lruSeq, String what, Object obj, ProcessRecord srcApp) {
+ private int offerLruProcessInternalLSP(ProcessRecord app, long now, String what, Object obj,
+ ProcessRecord srcApp) {
app.setLastActivityTime(now);
if (app.hasActivitiesOrRecentTasks()) {
// Don't want to touch dependent processes that are hosting activities.
- return index;
+ return -1;
}
- int lrui = mLruProcesses.lastIndexOf(app);
+ final int lrui = mLruProcesses.lastIndexOf(app);
if (lrui < 0) {
Slog.wtf(TAG, "Adding dependent process " + app + " not on LRU list: "
+ what + " " + obj + " from " + srcApp);
- return index;
}
+ return lrui;
+ }
- if (lrui >= index) {
- // Don't want to cause this to move dependent processes *back* in the
- // list as if they were less frequently used.
- return index;
- }
+ /**
+ * This method is called after the indices array is populated by the indices offered by
+ * {@link #offerLruProcessInternalLSP} to actually move the processes to the desired locations
+ * in the LRU list. Since the indices array is a SparseBooleanArray, the indices are sorted
+ * and this allows us to preserve the previous order of the processes relative to each other.
+ * Key of the indices array holds the current index of the process in the LRU list and the value
+ * is a boolean indicating whether the process is an activity process or not. Activity processes
+ * are moved to the nextActivityIndex and non-activity processes are moved to the nextIndex
+ * positions, which are provided by the caller.
+ *
+ * @param indices The indices of the processes to move.
+ * @param nextActivityIndex The next index to insert an activity process.
+ * @param nextIndex The next index to insert a non-activity process.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ private void completeLruProcessInternalLSP(SparseBooleanArray indices, int nextActivityIndex,
+ int nextIndex) {
+ for (int i = indices.size() - 1; i >= 0; i--) {
+ final int lrui = indices.keyAt(i);
+ if (lrui < 0) {
+ // Rest of the indices are invalid, we can return early.
+ return;
+ }
+ final boolean isActivity = indices.valueAt(i);
+ int index = isActivity ? nextActivityIndex : nextIndex;
- if (lrui >= mLruProcessActivityStart && index < mLruProcessActivityStart) {
- // Don't want to touch dependent processes that are hosting activities.
- return index;
- }
+ if (lrui >= index) {
+ // Don't want to cause this to move dependent processes *back* in the
+ // list as if they were less frequently used.
+ continue;
+ }
- mLruProcesses.remove(lrui);
- if (index > 0) {
+ final ProcessRecord app = mLruProcesses.remove(lrui);
index--;
+ if (DEBUG_LRU) Slog.d(TAG_LRU, "Moving dep from " + lrui + " to " + index
+ + " in LRU list: " + app);
+ mLruProcesses.add(index, app);
+ app.setLruSeq(mLruSeq);
+
+ if (isActivity) {
+ nextActivityIndex = index;
+ } else {
+ nextIndex = index;
+ }
}
- if (DEBUG_LRU) Slog.d(TAG_LRU, "Moving dep from " + lrui + " to " + index
- + " in LRU list: " + app);
- mLruProcesses.add(index, app);
- app.setLruSeq(lruSeq);
- return index;
}
/**
@@ -4058,6 +4084,15 @@
app.setLruSeq(mLruSeq);
+ // Key of the indices array holds the current index of the process in the LRU list and the
+ // value is a boolean indicating whether the process is an activity process or not.
+ // Activity processes will be moved to the nextActivityIndex and non-activity processes will
+ // be moved to the nextIndex positions when completeLruProcessInternalLSP is called.
+ // Since SparseBooleanArray's keys are sorted, we'll be able to keep the existing order of
+ // the processes relative to each other after the move.
+ final SparseBooleanArray indices = new SparseBooleanArray(psr.numberOfConnections()
+ + app.mProviders.numberOfProviderConnections());
+
// If the app is currently using a content provider or service,
// bump those processes as well.
for (int j = psr.numberOfConnections() - 1; j >= 0; j--) {
@@ -4069,16 +4104,12 @@
&& !cr.binding.service.app.isPersistent()) {
if (cr.binding.service.app.mServices.hasClientActivities()) {
if (nextActivityIndex >= 0) {
- nextActivityIndex = updateLruProcessInternalLSP(cr.binding.service.app,
- now,
- nextActivityIndex, mLruSeq,
- "service connection", cr, app);
+ indices.append(offerLruProcessInternalLSP(cr.binding.service.app, now,
+ "service connection", cr, app), true);
}
} else {
- nextIndex = updateLruProcessInternalLSP(cr.binding.service.app,
- now,
- nextIndex, mLruSeq,
- "service connection", cr, app);
+ indices.append(offerLruProcessInternalLSP(cr.binding.service.app, now,
+ "service connection", cr, app), false);
}
}
}
@@ -4086,10 +4117,11 @@
for (int j = ppr.numberOfProviderConnections() - 1; j >= 0; j--) {
ContentProviderRecord cpr = ppr.getProviderConnectionAt(j).provider;
if (cpr.proc != null && cpr.proc.getLruSeq() != mLruSeq && !cpr.proc.isPersistent()) {
- nextIndex = updateLruProcessInternalLSP(cpr.proc, now, nextIndex, mLruSeq,
- "provider reference", cpr, app);
+ indices.append(offerLruProcessInternalLSP(cpr.proc, now,
+ "provider reference", cpr, app), false);
}
}
+ completeLruProcessInternalLSP(indices, nextActivityIndex, nextIndex);
}
@GuardedBy(anyOf = {"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d206b20..fdf7dec 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -286,7 +286,6 @@
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CancellationException;
-import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -8049,7 +8048,14 @@
}
synchronized (mAbsoluteVolumeDeviceInfoMapLock) {
if (mAbsoluteVolumeDeviceInfoMap.containsKey(audioSystemDeviceOut)) {
- return mAbsoluteVolumeDeviceInfoMap.get(audioSystemDeviceOut).mDeviceVolumeBehavior;
+ final AbsoluteVolumeDeviceInfo deviceInfo = mAbsoluteVolumeDeviceInfoMap.get(
+ audioSystemDeviceOut);
+ if (deviceInfo != null) {
+ return deviceInfo.mDeviceVolumeBehavior;
+ }
+
+ Log.e(TAG,
+ "Null absolute volume device info stored for key " + audioSystemDeviceOut);
}
}
@@ -15043,6 +15049,11 @@
private void addAudioSystemDeviceOutToAbsVolumeDevices(int audioSystemDeviceOut,
AbsoluteVolumeDeviceInfo info) {
+ if (info == null) {
+ Log.e(TAG, "Cannot add null absolute volume info for audioSystemDeviceOut "
+ + audioSystemDeviceOut);
+ return;
+ }
if (DEBUG_VOL) {
Log.d(TAG, "Adding DeviceType: 0x" + Integer.toHexString(audioSystemDeviceOut)
+ " to mAbsoluteVolumeDeviceInfoMap with behavior "
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index a53b8df..c7a70fa 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -5666,6 +5666,11 @@
displayPowerController.stylusGestureStarted(eventTime);
}
}
+
+ @Override
+ public boolean isDisplayReadyForMirroring(int displayId) {
+ return mExternalDisplayPolicy.isDisplayReadyForMirroring(displayId);
+ }
}
class DesiredDisplayModeSpecsObserver
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index 28a0b28..f34d2cc 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -375,6 +375,54 @@
}
}
+ boolean isDisplayReadyForMirroring(int displayId) {
+ if (!mFlags.isWaitingConfirmationBeforeMirroringEnabled()) {
+ if (DEBUG) {
+ Slog.d(TAG, "isDisplayReadyForMirroring: mirroring CONFIRMED - "
+ + " flag 'waiting for confirmation before mirroring' is disabled");
+ }
+ return true;
+ }
+
+ synchronized (mSyncRoot) {
+ if (!mIsBootCompleted) {
+ if (DEBUG) {
+ Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - "
+ + "boot is in progress");
+ }
+ return false;
+ }
+
+ var logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId);
+ if (logicalDisplay == null) {
+ if (DEBUG) {
+ Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - "
+ + "logicalDisplay is null");
+ }
+ return false;
+ }
+
+ if (!isExternalDisplayLocked(logicalDisplay)) {
+ if (DEBUG) {
+ Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - "
+ + "logicalDisplay" + logicalDisplay.getDisplayIdLocked()
+ + " type is " + logicalDisplay.getDisplayInfoLocked().type);
+ }
+ return false;
+ }
+
+ if (!logicalDisplay.isEnabledLocked()) {
+ if (DEBUG) {
+ Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - "
+ + "logicalDisplay is disabled");
+ }
+ return false;
+ }
+ }
+
+ return true;
+ }
+
private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
@Override
public void notifyThrottling(@NonNull final Temperature temp) {
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 06a9103..09fa4e6 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -16,6 +16,7 @@
package com.android.server.display;
+import static android.hardware.devicestate.DeviceState.PROPERTY_EMULATED_ONLY;
import static android.hardware.devicestate.DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP;
import static android.hardware.devicestate.DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
@@ -594,6 +595,13 @@
boolean shouldDeviceBeWoken(DeviceState pendingState, DeviceState currentState,
boolean isInteractive, boolean isBootCompleted) {
if (mDeviceStateManagerFlags.deviceStatePropertyMigration()) {
+ if (currentState.hasProperties(PROPERTY_EMULATED_ONLY)
+ && !pendingState.hasProperties(PROPERTY_EMULATED_ONLY)) {
+ // Do not wake the device, since this transition may occur due to the user pressing
+ // the power button to exit an emulated state.
+ return false;
+ }
+
return pendingState.hasProperty(PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE)
&& !currentState.equals(INVALID_DEVICE_STATE)
&& !currentState.hasProperty(PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE)
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 99ced7f..b2e98bc 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -217,6 +217,11 @@
Flags::enableUserRefreshRateForExternalDisplay
);
+ private final FlagState mEnableWaitingConfirmationBeforeMirroring = new FlagState(
+ Flags.FLAG_ENABLE_WAITING_CONFIRMATION_BEFORE_MIRRORING,
+ Flags::enableWaitingConfirmationBeforeMirroring
+ );
+
private final FlagState mEnableBatteryStatsForAllDisplays = new FlagState(
Flags.FLAG_ENABLE_BATTERY_STATS_FOR_ALL_DISPLAYS,
Flags::enableBatteryStatsForAllDisplays
@@ -445,6 +450,14 @@
}
/**
+ * @return {@code true} if mirroring won't be enabled until boot completes and the user enables
+ * the display.
+ */
+ public boolean isWaitingConfirmationBeforeMirroringEnabled() {
+ return mEnableWaitingConfirmationBeforeMirroring.isEnabled();
+ }
+
+ /**
* @return {@code true} if battery stats is enabled for all displays, not just the primary
* display.
*/
@@ -511,6 +524,7 @@
pw.println(" " + mVirtualDisplayLimit);
pw.println(" " + mNormalBrightnessForDozeParameter);
pw.println(" " + mIdleScreenConfigInSubscribingLightSensor);
+ pw.println(" " + mEnableWaitingConfirmationBeforeMirroring);
pw.println(" " + mEnableBatteryStatsForAllDisplays);
pw.println(" " + mBlockAutobrightnessChangesOnStylusUsage);
pw.println(" " + mIsUserRefreshRateForExternalDisplayEnabled);
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 2f04d9e..df62638 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -367,6 +367,17 @@
}
flag {
+ name: "enable_waiting_confirmation_before_mirroring"
+ namespace: "display_manager"
+ description: "Allow ContentRecorder checking whether user confirmed mirroring after boot"
+ bug: "361698995"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_battery_stats_for_all_displays"
namespace: "display_manager"
description: "Flag to enable battery stats for all displays."
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index d70bd8b..9df2cb4 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -63,6 +63,9 @@
mObservers = Map.ofEntries(
Map.entry(Settings.System.getUriFor(Settings.System.POINTER_SPEED),
(reason) -> updateMousePointerSpeed()),
+ Map.entry(Settings.System.getUriFor(
+ Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING),
+ (reason) -> updateMouseReverseVerticalScrolling()),
Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_POINTER_SPEED),
(reason) -> updateTouchpadPointerSpeed()),
Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_NATURAL_SCROLLING),
@@ -163,6 +166,11 @@
mNative.setPointerSpeed(constrainPointerSpeedValue(speed));
}
+ private void updateMouseReverseVerticalScrolling() {
+ mNative.setMouseReverseVerticalScrollingEnabled(
+ InputSettings.isMouseReverseVerticalScrollingEnabled(mContext));
+ }
+
private void updateTouchpadPointerSpeed() {
mNative.setTouchpadPointerSpeed(
constrainPointerSpeedValue(InputSettings.getTouchpadPointerSpeed(mContext)));
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 4404d63..1f04dd9 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -127,6 +127,8 @@
void setMousePointerAccelerationEnabled(int displayId, boolean enabled);
+ void setMouseReverseVerticalScrollingEnabled(boolean enabled);
+
void setTouchpadPointerSpeed(int speed);
void setTouchpadNaturalScrollingEnabled(boolean enabled);
@@ -388,6 +390,9 @@
public native void setMousePointerAccelerationEnabled(int displayId, boolean enabled);
@Override
+ public native void setMouseReverseVerticalScrollingEnabled(boolean enabled);
+
+ @Override
public native void setTouchpadPointerSpeed(int speed);
@Override
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
index c02b103..404c841 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
@@ -19,7 +19,6 @@
import android.annotation.Nullable;
import android.os.Environment;
import android.security.keystore.recovery.KeyChainSnapshot;
-import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -29,9 +28,11 @@
import com.android.server.locksettings.recoverablekeystore.serialization
.KeyChainSnapshotParserException;
import com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSerializer;
+import com.android.server.utils.Slogf;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.cert.CertificateEncodingException;
@@ -81,12 +82,14 @@
public synchronized void put(int uid, KeyChainSnapshot snapshot) {
mSnapshotByUid.put(uid, snapshot);
- try {
- writeToDisk(uid, snapshot);
+ File snapshotFile = getSnapshotFile(uid);
+ try (FileOutputStream fileOutputStream = new FileOutputStream(snapshotFile)) {
+ KeyChainSnapshotSerializer.serialize(snapshot, fileOutputStream);
} catch (IOException | CertificateEncodingException e) {
- Log.e(TAG,
- String.format(Locale.US, "Error persisting snapshot for %d to disk", uid),
- e);
+ // If we fail to write the latest snapshot, we should delete any older snapshot that
+ // happens to be around. Otherwise snapshot syncs might end up going 'back in time'.
+ snapshotFile.delete();
+ Slogf.e(TAG, e, "Error persisting snapshot for %d to disk", uid);
}
}
@@ -100,10 +103,17 @@
return snapshot;
}
- try {
- return readFromDisk(uid);
+ File snapshotFile = getSnapshotFile(uid);
+ try (FileInputStream fileInputStream = new FileInputStream(snapshotFile)) {
+ return KeyChainSnapshotDeserializer.deserialize(fileInputStream);
+ } catch (FileNotFoundException e) {
+ Slogf.i(TAG, "Snapshot for uid %d not found", uid);
+ return null;
} catch (IOException | KeyChainSnapshotParserException e) {
- Log.e(TAG, String.format(Locale.US, "Error reading snapshot for %d from disk", uid), e);
+ // If we fail to read the latest snapshot, we should delete it in case it is in some way
+ // corrupted. We can regenerate snapshots anyway.
+ snapshotFile.delete();
+ Slogf.e(TAG, e, "Error reading snapshot for %d from disk", uid);
return null;
}
}
@@ -116,50 +126,6 @@
getSnapshotFile(uid).delete();
}
- /**
- * Writes the snapshot for recovery agent {@code uid} to disk.
- *
- * @throws IOException if an IO error occurs writing to disk.
- */
- private void writeToDisk(int uid, KeyChainSnapshot snapshot)
- throws IOException, CertificateEncodingException {
- File snapshotFile = getSnapshotFile(uid);
-
- try (
- FileOutputStream fileOutputStream = new FileOutputStream(snapshotFile)
- ) {
- KeyChainSnapshotSerializer.serialize(snapshot, fileOutputStream);
- } catch (IOException | CertificateEncodingException e) {
- // If we fail to write the latest snapshot, we should delete any older snapshot that
- // happens to be around. Otherwise snapshot syncs might end up going 'back in time'.
- snapshotFile.delete();
- throw e;
- }
- }
-
- /**
- * Reads the last snapshot for recovery agent {@code uid} from disk.
- *
- * @return The snapshot, or null if none existed.
- * @throws IOException if an IO error occurs reading from disk.
- */
- @Nullable
- private KeyChainSnapshot readFromDisk(int uid)
- throws IOException, KeyChainSnapshotParserException {
- File snapshotFile = getSnapshotFile(uid);
-
- try (
- FileInputStream fileInputStream = new FileInputStream(snapshotFile)
- ) {
- return KeyChainSnapshotDeserializer.deserialize(fileInputStream);
- } catch (IOException | KeyChainSnapshotParserException e) {
- // If we fail to read the latest snapshot, we should delete it in case it is in some way
- // corrupted. We can regenerate snapshots anyway.
- snapshotFile.delete();
- throw e;
- }
- }
-
private File getSnapshotFile(int uid) {
File folder = getStorageFolder();
String fileName = getSnapshotFileName(uid);
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 1f79ac0..089bbb7 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -16,7 +16,6 @@
package com.android.server.pm;
-import static android.content.pm.Flags.improveInstallFreeze;
import static android.content.pm.PackageInstaller.SessionParams.USER_ACTION_UNSPECIFIED;
import static android.content.pm.PackageManager.INSTALL_REASON_UNKNOWN;
import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT;
@@ -1050,13 +1049,13 @@
}
public void onFreezeStarted() {
- if (mPackageMetrics != null && improveInstallFreeze()) {
+ if (mPackageMetrics != null) {
mPackageMetrics.onStepStarted(PackageMetrics.STEP_FREEZE_INSTALL);
}
}
public void onFreezeCompleted() {
- if (mPackageMetrics != null && improveInstallFreeze()) {
+ if (mPackageMetrics != null) {
mPackageMetrics.onStepFinished(PackageMetrics.STEP_FREEZE_INSTALL);
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 8bab9de..708e067 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1101,7 +1101,7 @@
if (android.multiuser.Flags.cachesNotInvalidatedAtStartReadOnly()) {
UserManager.invalidateIsUserUnlockedCache();
UserManager.invalidateQuietModeEnabledCache();
- UserManager.invalidateUserSerialNumberCache();
+ UserManager.invalidateCacheOnUserListChange();
}
}
@@ -4448,7 +4448,7 @@
if (userData != null) {
synchronized (mUsersLock) {
- mUsers.put(userData.info.id, userData);
+ addUserDataLU(userData);
if (mNextSerialNumber < 0
|| mNextSerialNumber <= userData.info.id) {
mNextSerialNumber = userData.info.id + 1;
@@ -5724,7 +5724,7 @@
userData.info = userInfo;
userData.userProperties = new UserProperties(
userTypeDetails.getDefaultUserPropertiesReference());
- mUsers.put(userId, userData);
+ addUserDataLU(userData);
}
writeUserLP(userData);
writeUserListLP();
@@ -6138,7 +6138,7 @@
final UserData userData = new UserData();
userData.info = userInfo;
synchronized (mUsersLock) {
- mUsers.put(userInfo.id, userData);
+ addUserDataLU(userData);
}
updateUserIds();
return userData;
@@ -6148,8 +6148,7 @@
@VisibleForTesting
void removeUserInfo(@UserIdInt int userId) {
synchronized (mUsersLock) {
- UserManager.invalidateUserSerialNumberCache();
- mUsers.remove(userId);
+ removeUserDataLU(userId);
}
}
@@ -6579,8 +6578,7 @@
// Remove this user from the list
synchronized (mUsersLock) {
- UserManager.invalidateUserSerialNumberCache();
- mUsers.remove(userId);
+ removeUserDataLU(userId);
mIsUserManaged.delete(userId);
}
synchronized (mUserStates) {
@@ -6969,6 +6967,26 @@
}
/**
+ * Adding user data to mUsers list in one place to invalidate related caches.
+ */
+ @GuardedBy("mUsersLock")
+ private void addUserDataLU(UserData userData) {
+ if (android.multiuser.Flags.invalidateCacheOnUsersChangedReadOnly()) {
+ UserManager.invalidateCacheOnUserListChange();
+ }
+ mUsers.put(userData.info.id, userData);
+ }
+
+ /**
+ * Removing user data to mUsers list in one place to invalidate related caches.
+ */
+ @GuardedBy("mUsersLock")
+ private void removeUserDataLU(@UserIdInt int userId) {
+ UserManager.invalidateCacheOnUserListChange();
+ mUsers.remove(userId);
+ }
+
+ /**
* Caches the list of user ids in an array, adjusting the array size when necessary.
*/
private void updateUserIds() {
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index bc33946..0b5872b 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -285,6 +285,11 @@
}
}
+ private boolean isDisplayReadyForMirroring() {
+ return mDisplayContent.getDisplayInfo().type != Display.TYPE_EXTERNAL
+ || mDisplayContent.mWmService.mDisplayManagerInternal.isDisplayReadyForMirroring(
+ mDisplayContent.getDisplayId());
+ }
/**
* Ensure recording does not fall back to the display stack; ensure the recording is stopped
@@ -335,7 +340,7 @@
return;
}
- if (mContentRecordingSession.isWaitingForConsent()) {
+ if (mContentRecordingSession.isWaitingForConsent() || !isDisplayReadyForMirroring()) {
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, "Content Recording: waiting to record, so do "
+ "nothing");
return;
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index efca902..f36e680 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -337,6 +337,7 @@
int32_t getMousePointerSpeed();
void setPointerSpeed(int32_t speed);
void setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId, bool enabled);
+ void setMouseReverseVerticalScrollingEnabled(bool enabled);
void setTouchpadPointerSpeed(int32_t speed);
void setTouchpadNaturalScrollingEnabled(bool enabled);
void setTouchpadTapToClickEnabled(bool enabled);
@@ -482,6 +483,9 @@
// True if stylus button reporting through motion events is enabled.
bool stylusButtonMotionEventsEnabled{true};
+ // True if mouse vertical scrolling is reversed.
+ bool mouseReverseVerticalScrollingEnabled{false};
+
// The touchpad pointer speed, as a number from -7 (slowest) to 7 (fastest).
int32_t touchpadPointerSpeed{0};
@@ -762,6 +766,9 @@
outConfig->defaultPointerDisplayId = mLocked.pointerDisplayId;
+ outConfig->mouseReverseVerticalScrollingEnabled =
+ mLocked.mouseReverseVerticalScrollingEnabled;
+
outConfig->touchpadPointerSpeed = mLocked.touchpadPointerSpeed;
outConfig->touchpadNaturalScrollingEnabled = mLocked.touchpadNaturalScrollingEnabled;
outConfig->touchpadTapToClickEnabled = mLocked.touchpadTapToClickEnabled;
@@ -1317,6 +1324,21 @@
return mLocked.pointerSpeed;
}
+void NativeInputManager::setMouseReverseVerticalScrollingEnabled(bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ if (mLocked.mouseReverseVerticalScrollingEnabled == enabled) {
+ return;
+ }
+
+ mLocked.mouseReverseVerticalScrollingEnabled = enabled;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::Change::MOUSE_SETTINGS);
+}
+
void NativeInputManager::setPointerSpeed(int32_t speed) {
{ // acquire lock
std::scoped_lock _l(mLock);
@@ -3002,6 +3024,12 @@
return static_cast<jint>(im->getInputManager()->getReader().getLastUsedInputDeviceId());
}
+static void nativeSetMouseReverseVerticalScrollingEnabled(JNIEnv* env, jobject nativeImplObj,
+ bool enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->setMouseReverseVerticalScrollingEnabled(enabled);
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gInputManagerMethods[] = {
@@ -3048,6 +3076,8 @@
{"setPointerSpeed", "(I)V", (void*)nativeSetPointerSpeed},
{"setMousePointerAccelerationEnabled", "(IZ)V",
(void*)nativeSetMousePointerAccelerationEnabled},
+ {"setMouseReverseVerticalScrollingEnabled", "(Z)V",
+ (void*)nativeSetMouseReverseVerticalScrollingEnabled},
{"setTouchpadPointerSpeed", "(I)V", (void*)nativeSetTouchpadPointerSpeed},
{"setTouchpadNaturalScrollingEnabled", "(Z)V",
(void*)nativeSetTouchpadNaturalScrollingEnabled},
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index 2836d46..2add5b0 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -349,7 +349,7 @@
return nullptr;
}
- // Return the currently watched pids. The lock must be held.
+ // Return the currently watched pids as a comma-separated list. The lock must be held.
std::string watchedPidsLocked() const {
if (watched_.size() == 0) return "none";
bool first = true;
@@ -357,6 +357,7 @@
for (auto i = watched_.cbegin(); i != watched_.cend(); i++) {
if (first) {
result += StringPrintf("%d", *i);
+ first = false;
} else {
result += StringPrintf(",%d", *i);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
index f728168..782262d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -18,6 +18,7 @@
import static android.hardware.display.DisplayManagerGlobal.EVENT_DISPLAY_CONNECTED;
import static android.view.Display.TYPE_EXTERNAL;
+import static android.view.Display.TYPE_INTERNAL;
import static com.google.common.truth.Truth.assertThat;
@@ -36,6 +37,7 @@
import android.os.IThermalService;
import android.os.RemoteException;
import android.os.Temperature;
+import android.view.Display;
import android.view.DisplayInfo;
import androidx.test.filters.SmallTest;
@@ -97,6 +99,8 @@
@Mock
private LogicalDisplay mMockedLogicalDisplay;
@Mock
+ private LogicalDisplay mMockedDefaultDisplay;
+ @Mock
private DisplayNotificationManager mMockedDisplayNotificationManager;
@Mock
private ExternalDisplayStatsService mMockedExternalDisplayStatsService;
@@ -141,6 +145,15 @@
when(mMockedLogicalDisplay.getDisplayInfoLocked()).thenReturn(mockedLogicalDisplayInfo);
when(mMockedLogicalDisplayMapper.getDisplayLocked(EXTERNAL_DISPLAY_ID)).thenReturn(
mMockedLogicalDisplay);
+
+ // Initialize default logical display
+ when(mMockedDefaultDisplay.getDisplayIdLocked()).thenReturn(Display.DEFAULT_DISPLAY);
+ when(mMockedDefaultDisplay.isEnabledLocked()).thenReturn(true);
+ final var mockedDefaultDisplayInfo = new DisplayInfo();
+ mockedDefaultDisplayInfo.type = TYPE_INTERNAL;
+ when(mMockedDefaultDisplay.getDisplayInfoLocked()).thenReturn(mockedDefaultDisplayInfo);
+ when(mMockedLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY)).thenReturn(
+ mMockedDefaultDisplay);
}
@Test
@@ -293,6 +306,52 @@
verify(mMockedLogicalDisplayMapper, never()).forEachLocked(any());
}
+ @Test
+ public void testMirroringAlwaysConfirmedByUser_flagDisabled() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(false);
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(EXTERNAL_DISPLAY_ID)).isTrue();
+ }
+
+ @Test
+ public void testMirroringConfirmed_afterBootForEnabledDisplay() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true);
+ mExternalDisplayPolicy.onBootCompleted();
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(EXTERNAL_DISPLAY_ID))
+ .isTrue();
+ }
+
+ @Test
+ public void testMirroringNotConfirmed_afterBootForDisabledDisplay() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true);
+ mExternalDisplayPolicy.onBootCompleted();
+ when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(false);
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(EXTERNAL_DISPLAY_ID))
+ .isFalse();
+ }
+
+ @Test
+ public void testMirroringNeverConfirmed_forNonExternalDisplays() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true);
+ mExternalDisplayPolicy.onBootCompleted();
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(Display.DEFAULT_DISPLAY))
+ .isFalse();
+ }
+
+ @Test
+ public void testMirroringNeverConfirmed_forNonExistingDisplays() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true);
+ mExternalDisplayPolicy.onBootCompleted();
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(Display.INVALID_DISPLAY))
+ .isFalse();
+ }
+
+ @Test
+ public void testMirroringNeverConfirmed_duringBoot() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true);
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(EXTERNAL_DISPLAY_ID))
+ .isFalse();
+ }
+
private void setTemperature(final IThermalEventListener thermalEventListener,
final List<Temperature> temperature) throws RemoteException {
for (var t : temperature) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 1729ad5..d831cf8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -121,6 +121,8 @@
Set.of(DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE), Collections.emptySet());
private static final DeviceState DEVICE_STATE_OPEN = createDeviceState(2, "Two",
Set.of(DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE), Collections.emptySet());
+ private static final DeviceState DEVICE_STATE_EMULATED = createDeviceState(3, "Three",
+ Set.of(DeviceState.PROPERTY_EMULATED_ONLY), Collections.emptySet());
private static final int FLAG_GO_TO_SLEEP_ON_FOLD = 0;
private static final int FLAG_GO_TO_SLEEP_FLAG_SOFT_SLEEP = 2;
private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
@@ -686,6 +688,14 @@
}
@Test
+ public void testDeviceShouldNotBeWokenWhenExitingEmulatedState() {
+ assertFalse(mLogicalDisplayMapper.shouldDeviceBeWoken(DEVICE_STATE_OPEN,
+ DEVICE_STATE_EMULATED,
+ /* isInteractive= */false,
+ /* isBootCompleted= */true));
+ }
+
+ @Test
public void testDeviceShouldBePutToSleep() {
assertTrue(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
DEVICE_STATE_OPEN,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
index 5676a38..6d14065 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -459,7 +459,6 @@
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
newInjector.setReadStream(bais);
newDataStore.loadIfNeeded();
- assertNotNull(newDataStore.getUserPreferredRefreshRate(testDisplayDevice));
assertEquals(85.3f, mDataStore.getUserPreferredRefreshRate(testDisplayDevice), 01.f);
assertEquals(85.3f, newDataStore.getUserPreferredRefreshRate(testDisplayDevice), 0.1f);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 584fd62..40b9c61 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -25,6 +25,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.job.Flags.FLAG_COUNT_QUOTA_FIX;
+import static com.android.server.job.Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
@@ -303,6 +304,12 @@
}
}
+ private int getProcessStateQuotaFreeThreshold() {
+ synchronized (mQuotaController.mLock) {
+ return mQuotaController.getProcessStateQuotaFreeThreshold();
+ }
+ }
+
private void setProcessState(int procState) {
setProcessState(procState, mSourceUid);
}
@@ -315,7 +322,7 @@
final boolean contained = foregroundUids.get(uid);
mUidObserver.onUidStateChanged(uid, procState, 0,
ActivityManager.PROCESS_CAPABILITY_NONE);
- if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ if (procState <= getProcessStateQuotaFreeThreshold()) {
if (!contained) {
verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1))
.put(eq(uid), eq(true));
@@ -1371,7 +1378,7 @@
}
setDischarging();
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
assertEquals(timeUntilQuotaConsumedMs,
mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
@@ -1473,7 +1480,7 @@
}
setDischarging();
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
@@ -1505,7 +1512,7 @@
createTimingSession(sElapsedRealtimeClock.millis() - mQcConstants.EJ_WINDOW_SIZE_MS,
timeUsedMs, 5), true);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
@@ -4126,7 +4133,7 @@
}
advanceElapsedClock(5 * SECOND_IN_MILLIS);
// Change to a state that should still be considered foreground.
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
advanceElapsedClock(5 * SECOND_IN_MILLIS);
synchronized (mQuotaController.mLock) {
mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
@@ -4134,6 +4141,36 @@
assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
}
+ /** Tests that Timers count FOREGROUND_SERVICE jobs. */
+ @Test
+ @RequiresFlagsEnabled(FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS)
+ public void testTimerTracking_Fgs() {
+ setDischarging();
+
+ JobStatus jobStatus = createJobStatus("testTimerTracking_Fgs", 1);
+ setProcessState(ActivityManager.PROCESS_STATE_BOUND_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+ }
+
+ assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobStatus);
+ }
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ // Change to FOREGROUND_SERVICE state that should count.
+ setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ long start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
+ }
+ List<TimingSession> expected = new ArrayList<>();
+ expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
/**
* Tests that Timers properly track sessions when switching between foreground and background
* states.
@@ -4180,7 +4217,7 @@
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg3);
}
@@ -4213,7 +4250,7 @@
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg3);
}
@@ -4262,7 +4299,7 @@
}
assertEquals(0, stats.jobCountInRateLimitingWindow);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg1);
}
@@ -4412,7 +4449,7 @@
mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
}
advanceElapsedClock(5 * SECOND_IN_MILLIS);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg1);
}
@@ -4625,7 +4662,7 @@
// App still in foreground so everything should be in quota.
advanceElapsedClock(20 * SECOND_IN_MILLIS);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
@@ -5901,7 +5938,7 @@
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg3);
}
@@ -5935,7 +5972,7 @@
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg3);
}
@@ -6056,7 +6093,7 @@
mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
}
advanceElapsedClock(5 * SECOND_IN_MILLIS);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg1);
}
@@ -6534,7 +6571,7 @@
// App still in foreground so everything should be in quota.
advanceElapsedClock(20 * SECOND_IN_MILLIS);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
assertTrue(jobTop2.isExpeditedQuotaApproved());
assertTrue(jobFg.isExpeditedQuotaApproved());
assertTrue(jobBg.isExpeditedQuotaApproved());
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index e443696..c51261f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -52,6 +52,7 @@
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.view.ContentRecordingSession;
+import android.view.Display;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.SurfaceControl;
@@ -93,9 +94,11 @@
private boolean mHandleAnisotropicDisplayMirroring = false;
@Before public void setUp() {
+ mDisplayInfo.type = Display.TYPE_VIRTUAL;
MockitoAnnotations.initMocks(this);
doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
+ doReturn(false).when(mWm.mDisplayManagerInternal).isDisplayReadyForMirroring(anyInt());
// Skip unnecessary operations of relayout.
spyOn(mWm.mWindowPlacerLocked);
@@ -163,6 +166,25 @@
}
@Test
+ public void testUpdateRecording_externalDisplayWithoutUserConfirmation() {
+ mDisplayInfo.type = Display.TYPE_EXTERNAL;
+ defaultInit();
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
+ }
+
+ @Test
+ public void testUpdateRecording_externalDisplayWithUserConfirmation() {
+ doReturn(true).when(mWm.mDisplayManagerInternal).isDisplayReadyForMirroring(anyInt());
+ mDisplayInfo.type = Display.TYPE_EXTERNAL;
+ defaultInit();
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+ }
+
+ @Test
public void testUpdateRecording_display_invalidDisplayIdToMirror() {
defaultInit();
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
index 5e8f347..c8fc482 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
@@ -26,7 +26,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertFalse;
@@ -73,7 +72,6 @@
when(mMockWindowState.getRequestedVisibleTypes()).thenReturn(0);
when(mMockActivityRecord.findMainWindow()).thenReturn(mMockWindowState);
- spy(mDisplayContent);
doReturn(mMockActivityRecord).when(mDisplayContent).topRunningActivity();
when(mDisplayContent.getIgnoreOrientationRequest()).thenReturn(true);