Merge "Mass Robolectric migration systemui/[n-r]* [tvwxyz]*" into main
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
index 459c286..515ddc8 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
@@ -19,16 +19,13 @@
import android.os.Bundle;
import android.os.SystemClock;
import android.perftests.utils.ShellHelper;
-import android.util.Log;
import java.util.ArrayList;
// Based on //platform/frameworks/base/apct-tests/perftests/utils/BenchmarkState.java
public class BenchmarkRunner {
- private static final String TAG = BenchmarkRunner.class.getSimpleName();
+
private static final long COOL_OFF_PERIOD_MS = 1000;
- private static final int CPU_IDLE_TIMEOUT_MS = 60 * 1000;
- private static final int CPU_IDLE_THRESHOLD_PERCENTAGE = 90;
private static final int NUM_ITERATIONS = 4;
@@ -83,7 +80,7 @@
private void prepareForNextRun() {
SystemClock.sleep(COOL_OFF_PERIOD_MS);
- waitCoolDownPeriod();
+ ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers");
mStartTimeNs = System.nanoTime();
mPausedDurationNs = 0;
}
@@ -105,7 +102,7 @@
* to avoid unnecessary waiting.
*/
public void resumeTiming() {
- waitCoolDownPeriod();
+ ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers");
resumeTimer();
}
@@ -165,56 +162,4 @@
}
return null;
}
-
- /** Waits for the broadcast queue and the CPU cores to be idle. */
- public void waitCoolDownPeriod() {
- waitForBroadcastIdle();
- waitForCpuIdle();
- }
-
- private void waitForBroadcastIdle() {
- Log.d(TAG, "starting to waitForBroadcastIdle");
- final long startedAt = System.currentTimeMillis();
- ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers");
- final long elapsed = System.currentTimeMillis() - startedAt;
- Log.d(TAG, "waitForBroadcastIdle is complete in " + elapsed + " ms");
- }
- private void waitForCpuIdle() {
- Log.d(TAG, "starting to waitForCpuIdle");
- final long startedAt = System.currentTimeMillis();
- while (true) {
- final int idleCpuPercentage = getIdleCpuPercentage();
- final long elapsed = System.currentTimeMillis() - startedAt;
- Log.d(TAG, "waitForCpuIdle " + idleCpuPercentage + "% (" + elapsed + "ms elapsed)");
- if (idleCpuPercentage >= CPU_IDLE_THRESHOLD_PERCENTAGE) {
- Log.d(TAG, "waitForCpuIdle is complete in " + elapsed + " ms");
- return;
- }
- if (elapsed >= CPU_IDLE_TIMEOUT_MS) {
- Log.e(TAG, "Ending waitForCpuIdle because it didn't finish in "
- + CPU_IDLE_TIMEOUT_MS + " ms");
- return;
- }
- SystemClock.sleep(1000);
- }
- }
-
- private int getIdleCpuPercentage() {
- String output = ShellHelper.runShellCommand("top -m 1 -n 1");
- String[] tokens = output.split("\\s+");
- float totalCpu = -1;
- float idleCpu = -1;
- for (String token : tokens) {
- if (token.contains("%cpu")) {
- totalCpu = Float.parseFloat(token.split("%")[0]);
- } else if (token.contains("%idle")) {
- idleCpu = Float.parseFloat(token.split("%")[0]);
- }
- }
- if (totalCpu < 0 || idleCpu < 0) {
- Log.e(TAG, "Could not get idle cpu percentage, output=" + output);
- return -1;
- }
- return (int) (100 * idleCpu / totalCpu);
- }
}
\ No newline at end of file
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 98ab0c2..762e2af 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -188,6 +188,21 @@
}
}
+ /** Tests creating a new user, with wait times between iterations. */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void createUser_realistic() throws RemoteException {
+ while (mRunner.keepRunning()) {
+ Log.i(TAG, "Starting timer");
+ final int userId = createUserNoFlags();
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
/** Tests creating and starting a new user. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void createAndStartUser() throws RemoteException {
@@ -224,6 +239,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
+ waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -238,6 +254,7 @@
mRunner.pauseTiming();
final int userId = createUserNoFlags();
+ waitForBroadcastIdle();
runThenWaitForBroadcasts(userId, () -> {
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -292,6 +309,9 @@
preStartUser(userId, numberOfIterationsToSkip);
+ waitForBroadcastIdle();
+ waitCoolDownPeriod();
+
runThenWaitForBroadcasts(userId, () -> {
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -333,6 +353,9 @@
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
+ waitForBroadcastIdle();
+ waitCoolDownPeriod();
+
runThenWaitForBroadcasts(userId, () -> {
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -397,6 +420,7 @@
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
+ waitCoolDownPeriod();
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -430,6 +454,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
+ waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -441,7 +466,6 @@
mRunner.pauseTiming();
final int startUser = mAm.getCurrentUser();
final int userId = createUserNoFlags();
-
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -455,6 +479,27 @@
}
}
+ /** Tests switching to an uninitialized user with wait times between iterations. */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void switchUser_realistic() throws Exception {
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int startUser = ActivityManager.getCurrentUser();
+ final int userId = createUserNoFlags();
+ waitCoolDownPeriod();
+ Log.d(TAG, "Starting timer");
+ mRunner.resumeTiming();
+
+ switchUser(userId);
+
+ mRunner.pauseTiming();
+ Log.d(TAG, "Stopping timer");
+ switchUserNoCheck(startUser);
+ removeUser(userId);
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
/** Tests switching to a previously-started, but no-longer-running, user. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void switchUser_stopped() throws RemoteException {
@@ -462,7 +507,6 @@
mRunner.pauseTiming();
final int startUser = mAm.getCurrentUser();
final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ true);
-
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -492,6 +536,7 @@
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
+ waitCoolDownPeriod();
Log.d(TAG, "Starting timer");
mRunner.resumeTiming();
@@ -517,6 +562,7 @@
/* useStaticWallpaper */true);
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
+ waitCoolDownPeriod();
Log.d(TAG, "Starting timer");
mRunner.resumeTiming();
@@ -560,6 +606,7 @@
final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ false);
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
+ waitCoolDownPeriod();
Log.d(TAG, "Starting timer");
mRunner.resumeTiming();
@@ -567,6 +614,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
+ waitForBroadcastIdle();
switchUserNoCheck(startUser);
mRunner.resumeTimingForNextIteration();
}
@@ -583,6 +631,7 @@
/* useStaticWallpaper */ true);
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
+ waitCoolDownPeriod();
Log.d(TAG, "Starting timer");
mRunner.resumeTiming();
@@ -590,6 +639,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
+ waitForBroadcastIdle();
switchUserNoCheck(startUser);
mRunner.resumeTimingForNextIteration();
}
@@ -625,11 +675,13 @@
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void stopUser_realistic() throws RemoteException {
final int userId = createUserNoFlags();
+ waitCoolDownPeriod();
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
runThenWaitForBroadcasts(userId, ()-> {
mIam.startUserInBackground(userId);
}, Intent.ACTION_USER_STARTED, Intent.ACTION_MEDIA_MOUNTED);
+ waitCoolDownPeriod();
Log.d(TAG, "Starting timer");
mRunner.resumeTiming();
@@ -651,7 +703,7 @@
final int startUser = mAm.getCurrentUser();
final int userId = createUserNoFlags();
- mRunner.waitCoolDownPeriod();
+ waitForBroadcastIdle();
mUserSwitchWaiter.runThenWaitUntilBootCompleted(userId, () -> {
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -674,7 +726,7 @@
final int startUser = ActivityManager.getCurrentUser();
final int userId = createUserNoFlags();
- mRunner.waitCoolDownPeriod();
+ waitCoolDownPeriod();
mUserSwitchWaiter.runThenWaitUntilBootCompleted(userId, () -> {
mRunner.resumeTiming();
Log.d(TAG, "Starting timer");
@@ -700,7 +752,7 @@
switchUser(userId);
}, Intent.ACTION_MEDIA_MOUNTED);
- mRunner.waitCoolDownPeriod();
+ waitForBroadcastIdle();
mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(startUser, () -> {
runThenWaitForBroadcasts(userId, () -> {
mRunner.resumeTiming();
@@ -729,7 +781,7 @@
switchUser(userId);
}, Intent.ACTION_MEDIA_MOUNTED);
- mRunner.waitCoolDownPeriod();
+ waitCoolDownPeriod();
mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(startUser, () -> {
runThenWaitForBroadcasts(userId, () -> {
mRunner.resumeTiming();
@@ -775,6 +827,7 @@
Log.d(TAG, "Stopping timer");
attestTrue("Failed creating profile " + userId, mUm.isManagedProfile(userId));
removeUser(userId);
+ waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -815,6 +868,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
+ waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -859,6 +913,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
+ waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
removeUser(userId);
@@ -910,6 +965,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
+ waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -974,6 +1030,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
+ waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -1014,6 +1071,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
+ waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -1066,6 +1124,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
+ waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -1105,6 +1164,7 @@
runThenWaitForBroadcasts(userId, () -> {
startUserInBackgroundAndWaitForUnlock(userId);
}, Intent.ACTION_MEDIA_MOUNTED);
+ waitCoolDownPeriod();
mRunner.resumeTiming();
Log.d(TAG, "Starting timer");
@@ -1220,7 +1280,6 @@
* If lack of success should fail the test, use {@link #switchUser(int)} instead.
*/
private boolean switchUserNoCheck(int userId) throws RemoteException {
- mRunner.waitCoolDownPeriod();
final boolean[] success = {true};
mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(userId, () -> {
mAm.switchUser(userId);
@@ -1237,7 +1296,7 @@
*/
private void stopUserAfterWaitingForBroadcastIdle(int userId)
throws RemoteException {
- mRunner.waitCoolDownPeriod();
+ waitForBroadcastIdle();
stopUser(userId);
}
@@ -1379,8 +1438,6 @@
*/
private void runThenWaitForBroadcasts(int userId, FunctionalUtils.ThrowingRunnable runnable,
String... actions) {
- mRunner.waitCoolDownPeriod();
-
final String unreceivedAction =
mBroadcastWaiter.runThenWaitForBroadcasts(userId, runnable, actions);
@@ -1481,4 +1538,28 @@
assertEquals("", ShellHelper.runShellCommand("setprop " + name + " " + value));
return TextUtils.firstNotEmpty(oldValue, "invalid");
}
+
+ private void waitForBroadcastIdle() {
+ try {
+ ShellHelper.runShellCommandWithTimeout(
+ "am wait-for-broadcast-idle --flush-broadcast-loopers", TIMEOUT_IN_SECOND);
+ } catch (TimeoutException e) {
+ Log.e(TAG, "Ending waitForBroadcastIdle because it is taking too long", e);
+ }
+ }
+
+ private void sleep(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+
+ private void waitCoolDownPeriod() {
+ // Heuristic value based on local tests. Stability increased compared to no waiting.
+ final int tenSeconds = 1000 * 10;
+ waitForBroadcastIdle();
+ sleep(tenSeconds);
+ }
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7dbf270..76c1ed6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -355,7 +355,7 @@
private static final String DEFAULT_FULL_BACKUP_AGENT = "android.app.backup.FullBackupAgent";
- private static final long BINDER_CALLBACK_THROTTLE_MS = 10_100L;
+ private static final long BINDER_CALLBACK_THROTTLE = 10_100L;
private long mBinderCallbackLast = -1;
/**
@@ -7551,13 +7551,12 @@
@Override
public void onTransactionError(int pid, int code, int flags, int err) {
final long now = SystemClock.uptimeMillis();
- if (now < mBinderCallbackLast + BINDER_CALLBACK_THROTTLE_MS) {
+ if (now < mBinderCallbackLast + BINDER_CALLBACK_THROTTLE) {
Slog.d(TAG, "Too many transaction errors, throttling freezer binder callback.");
return;
}
mBinderCallbackLast = now;
try {
- Log.wtfStack(TAG, "Binder Transaction Error");
mgr.frozenBinderTransactionDetected(pid, code, flags, err);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 44444b5..67752f2 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -62,6 +62,7 @@
import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED;
import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED;
import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
+import static android.app.admin.flags.Flags.onboardingConsentlessBugreports;
import static android.app.admin.flags.Flags.FLAG_IS_MTE_POLICY_ENFORCED;
import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
@@ -17626,6 +17627,17 @@
return onboardingBugreportV2Enabled();
}
+ // TODO(b/308755220): Remove once the build is finalised.
+ /**
+ * Returns true if the flag for consentless bugreports is enabled.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean isOnboardingConsentlessBugreportFlagEnabled() {
+ return onboardingConsentlessBugreports();
+ }
+
/**
* Returns the subscription ids of all subscriptions which were downloaded by the calling
* admin.
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index bbd0e9f..821034a 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -3468,8 +3468,12 @@
* Android S ({@link android.os.Build.VERSION_CODES#S API 31})</li>
* <li>{@link android.os.Build.VERSION_CODES#R API 30} or higher on
* Android T ({@link android.os.Build.VERSION_CODES#TIRAMISU API 33})</li>
- * <li>{@link android.os.Build.VERSION_CODES#S API 31} or higher <b>after</b>
- * Android T ({@link android.os.Build.VERSION_CODES#TIRAMISU API 33})</li>
+ * <li>{@link android.os.Build.VERSION_CODES#S API 31} or higher on
+ * Android U ({@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE API 34})
+ * </li>
+ * <li>{@link android.os.Build.VERSION_CODES#TIRAMISU API 33} or higher on
+ * Android V ({@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM API 35})
+ * </li>
* </ul>
* </li>
* <li>The installer is:
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index e2a131c..5668c54 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -140,6 +140,16 @@
}
}
+flag {
+ name: "fix_avatar_concurrent_file_write"
+ namespace: "multiuser"
+ description: "Fix potential unexpected behavior due to concurrent file writing"
+ bug: "339351031"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
# This flag guards the private space feature and all its implementations excluding the APIs. APIs are guarded by android.os.Flags.allow_private_profile.
flag {
name: "enable_private_space_features"
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 9eabc8d..53771e3 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -963,11 +963,10 @@
* originate from the system, just that we were unable to verify it. This can
* happen for a number of reasons during normal operation.
*
- * @param event The {@link android.view.InputEvent} to check
+ * @param event The {@link android.view.InputEvent} to check.
*
* @return {@link android.view.VerifiedInputEvent}, which is a subset of the provided
- * {@link android.view.InputEvent}
- * {@code null} if the event could not be verified.
+ * {@link android.view.InputEvent}, or {@code null} if the event could not be verified.
*/
@Nullable
public VerifiedInputEvent verifyInputEvent(@NonNull InputEvent event) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f357ebf..7933212 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -19930,6 +19930,12 @@
public static final String NETWORK_LOCATION_OPT_IN = "network_location_opt_in";
/**
+ * Whether haptics are enabled for Active Unlock on wear.
+ * @hide
+ */
+ public static final String VIBRATE_FOR_ACTIVE_UNLOCK = "wear_vibrate_for_active_unlock";
+
+ /**
* The custom foreground color.
* @hide
*/
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index ba1915c..15b0c13 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -2263,6 +2263,7 @@
this(modeId, width, height, refreshRate, vsyncRate, false, alternativeRefreshRates,
supportedHdrTypes);
}
+
/**
* @hide
*/
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index da86e2d..8b9d876 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -213,7 +213,7 @@
/**
* The supported modes that will be exposed externally.
- * Might have different set of modes that supportedModes for VRR displays
+ * Might have different set of modes than supportedModes for VRR displays
*/
public Display.Mode[] appsSupportedModes = Display.Mode.EMPTY_ARRAY;
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 6db40bf..79a9f2d 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -682,7 +682,8 @@
* <li>For a touch screen or touch pad, reports the approximate size of the contact area in
* relation to the maximum detectable size for the device. The value is normalized
* to a range from 0 (smallest detectable size) to 1 (largest detectable size),
- * although it is not a linear scale. This value is of limited use.
+ * although it is not a linear scale. The value of size can be used to
+ * determine fat touch events.
* To obtain calibrated size information, use
* {@link #AXIS_TOUCH_MAJOR} or {@link #AXIS_TOOL_MAJOR}.
* </ul>
@@ -2795,13 +2796,8 @@
}
/**
- * Returns the current pressure of this event for the given pointer
- * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
- * identifier for this index).
- * The pressure generally
- * ranges from 0 (no pressure at all) to 1 (normal pressure), however
- * values higher than 1 may be generated depending on the calibration of
- * the input device.
+ * Returns the value of {@link #AXIS_PRESSURE} for the given pointer <em>index</em>.
+ *
* @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
* (the first pointer that is down) to {@link #getPointerCount()}-1.
*
@@ -2812,14 +2808,8 @@
}
/**
- * Returns a scaled value of the approximate size for the given pointer
- * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
- * identifier for this index).
- * This represents some approximation of the area of the screen being
- * pressed; the actual value in pixels corresponding to the
- * touch is normalized with the device specific range of values
- * and scaled to a value between 0 and 1. The value of size can be used to
- * determine fat touch events.
+ * Returns the value of {@link #AXIS_SIZE} for the given pointer <em>index</em>.
+ *
* @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
* (the first pointer that is down) to {@link #getPointerCount()}-1.
*
@@ -2830,10 +2820,8 @@
}
/**
- * Returns the length of the major axis of an ellipse that describes the touch
- * area at the point of contact for the given pointer
- * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
- * identifier for this index).
+ * Returns the value of {@link #AXIS_TOUCH_MAJOR} for the given pointer <em>index</em>.
+ *
* @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
* (the first pointer that is down) to {@link #getPointerCount()}-1.
*
@@ -2844,10 +2832,8 @@
}
/**
- * Returns the length of the minor axis of an ellipse that describes the touch
- * area at the point of contact for the given pointer
- * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
- * identifier for this index).
+ * Returns the value of {@link #AXIS_TOUCH_MINOR} for the given pointer <em>index</em>.
+ *
* @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
* (the first pointer that is down) to {@link #getPointerCount()}-1.
*
@@ -2858,12 +2844,8 @@
}
/**
- * Returns the length of the major axis of an ellipse that describes the size of
- * the approaching tool for the given pointer
- * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
- * identifier for this index).
- * The tool area represents the estimated size of the finger or pen that is
- * touching the device independent of its actual touch area at the point of contact.
+ * Returns the value of {@link #AXIS_TOOL_MAJOR} for the given pointer <em>index</em>.
+ *
* @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
* (the first pointer that is down) to {@link #getPointerCount()}-1.
*
@@ -2874,12 +2856,8 @@
}
/**
- * Returns the length of the minor axis of an ellipse that describes the size of
- * the approaching tool for the given pointer
- * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
- * identifier for this index).
- * The tool area represents the estimated size of the finger or pen that is
- * touching the device independent of its actual touch area at the point of contact.
+ * Returns the value of {@link #AXIS_TOOL_MINOR} for the given pointer <em>index</em>.
+ *
* @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
* (the first pointer that is down) to {@link #getPointerCount()}-1.
*
@@ -2890,15 +2868,8 @@
}
/**
- * Returns the orientation of the touch area and tool area in radians clockwise from vertical
- * for the given pointer <em>index</em> (use {@link #getPointerId(int)} to find the pointer
- * identifier for this index).
- * An angle of 0 radians indicates that the major axis of contact is oriented
- * upwards, is perfectly circular or is of unknown orientation. A positive angle
- * indicates that the major axis of contact is oriented to the right. A negative angle
- * indicates that the major axis of contact is oriented to the left.
- * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
- * (finger pointing fully right).
+ * Returns the value of {@link #AXIS_ORIENTATION} for the given pointer <em>index</em>.
+ *
* @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
* (the first pointer that is down) to {@link #getPointerCount()}-1.
*
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 95c9d7b..5e3f09a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1984,9 +1984,25 @@
public @interface ContentSensitivity {}
/**
- * Automatically determine whether a view displays sensitive content. For example, available
- * autofill hints (or some other signal) can be used to determine if this view
- * displays sensitive content.
+ * Content sensitivity is determined by the framework. The framework uses a heuristic to
+ * determine if this view displays sensitive content.
+ * Autofill hints i.e. {@link #getAutofillHints()} are used in the heuristic
+ * to determine if this view should be considered as a sensitive view.
+ * <p>
+ * {@link #AUTOFILL_HINT_USERNAME},
+ * {@link #AUTOFILL_HINT_PASSWORD},
+ * {@link #AUTOFILL_HINT_CREDIT_CARD_NUMBER},
+ * {@link #AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE},
+ * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE},
+ * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY},
+ * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH},
+ * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR}
+ * are considered sensitive hints by the framework, and the list may include more hints
+ * in the future.
+ *
+ * <p> The window hosting a sensitive view will be marked as secure during an active media
+ * projection session. This would be equivalent to applying
+ * {@link android.view.WindowManager.LayoutParams#FLAG_SECURE} to the window.
*
* @see #getContentSensitivity()
*/
@@ -1996,6 +2012,10 @@
/**
* The view displays sensitive content.
*
+ * <p> The window hosting a sensitive view will be marked as secure during an active media
+ * projection session. This would be equivalent to applying
+ * {@link android.view.WindowManager.LayoutParams#FLAG_SECURE} to the window.
+ *
* @see #getContentSensitivity()
*/
@FlaggedApi(FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API)
@@ -10548,9 +10568,13 @@
/**
* Sets content sensitivity mode to determine whether this view displays sensitive content
- * (e.g. username, password etc.). The system may improve user privacy i.e. hide content
+ * (e.g. username, password etc.). The system will improve user privacy i.e. hide content
* drawn by a sensitive view from screen sharing and recording.
*
+ * <p> The window hosting a sensitive view will be marked as secure during an active media
+ * projection session. This would be equivalent to applying
+ * {@link android.view.WindowManager.LayoutParams#FLAG_SECURE} to the window.
+ *
* @param mode {@link #CONTENT_SENSITIVITY_AUTO}, {@link #CONTENT_SENSITIVITY_NOT_SENSITIVE}
* or {@link #CONTENT_SENSITIVITY_SENSITIVE}
*/
@@ -10574,8 +10598,7 @@
* {@link #setContentSensitivity(int)}.
*/
@FlaggedApi(FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API)
- public @ContentSensitivity
- final int getContentSensitivity() {
+ public @ContentSensitivity final int getContentSensitivity() {
return (mPrivateFlags4 & PFLAG4_CONTENT_SENSITIVITY_MASK)
>> PFLAG4_CONTENT_SENSITIVITY_SHIFT;
}
@@ -34061,10 +34084,14 @@
}
private float convertVelocityToFrameRate(float velocityPps) {
+ // From UXR study, premium experience is:
+ // 1500+ dp/s: 120fps
+ // 0 - 1500 dp/s: 80fps
+ // OEMs are likely to modify this to balance battery and user experience for their
+ // specific device.
float density = mAttachInfo.mDensity;
float velocityDps = velocityPps / density;
- // Choose a frame rate in increments of 10fps
- return Math.min(MAX_FRAME_RATE, 60f + (10f * (float) Math.floor(velocityDps / 300f)));
+ return (velocityDps >= 1500f) ? MAX_FRAME_RATE : 80f;
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 496e899..fd407d6 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2790,11 +2790,27 @@
public void bringChildToFront(View child) {
}
+ // keep in sync with getHostVisibilityReason
int getHostVisibility() {
return mView != null && (mAppVisible || mForceDecorViewVisibility)
? mView.getVisibility() : View.GONE;
}
+ String getHostVisibilityReason() {
+ if (mView == null) {
+ return "mView is null";
+ }
+ if (!mAppVisible && !mForceDecorViewVisibility) {
+ return "!mAppVisible && !mForceDecorViewVisibility";
+ }
+ switch (mView.getVisibility()) {
+ case View.VISIBLE: return "View.VISIBLE";
+ case View.GONE: return "View.GONE";
+ case View.INVISIBLE: return "View.INVISIBLE";
+ default: return "";
+ }
+ }
+
/**
* Add LayoutTransition to the list of transitions to be started in the next traversal.
* This list will be cleared after the transitions on the list are start()'ed. These
@@ -3346,6 +3362,7 @@
int desiredWindowHeight;
final int viewVisibility = getHostVisibility();
+ final String viewVisibilityReason = getHostVisibilityReason();
final boolean viewVisibilityChanged = !mFirst
&& (mViewVisibility != viewVisibility || mNewSurfaceNeeded
// Also check for possible double visibility update, which will make current
@@ -4220,7 +4237,7 @@
if (!isViewVisible) {
if (mLastTraversalWasVisible) {
- logAndTrace("Not drawing due to not visible");
+ logAndTrace("Not drawing due to not visible. Reason=" + viewVisibilityReason);
}
mLastPerformTraversalsSkipDrawReason = "view_not_visible";
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 0f54940b..42bf420 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2801,6 +2801,10 @@
* it from appearing in screenshots or from being viewed on non-secure
* displays.
*
+ * <p>See {@link android.view.View#setContentSensitivity(int)}, a window hosting
+ * a sensitive view will be marked as secure during media projection, preventing
+ * it from being viewed on non-secure displays and during screen share.
+ *
* <p>See {@link android.view.Display#FLAG_SECURE} for more details about
* secure surfaces and secure displays.
*/
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 3006e20..2068bd7 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -1411,10 +1411,8 @@
return JNI_TRUE;
}
- if (err == FAILED_TRANSACTION) {
- env->CallStaticVoidMethod(gBinderOffsets.mClass, gBinderOffsets.mTransactionCallback,
- getpid(), code, flags, err);
- }
+ env->CallStaticVoidMethod(gBinderOffsets.mClass, gBinderOffsets.mTransactionCallback, getpid(),
+ code, flags, err);
if (err == UNKNOWN_TRANSACTION) {
return JNI_FALSE;
diff --git a/core/tests/coretests/src/android/util/BinaryXmlTest.java b/core/tests/coretests/src/android/util/BinaryXmlTest.java
index 025e831..da29828 100644
--- a/core/tests/coretests/src/android/util/BinaryXmlTest.java
+++ b/core/tests/coretests/src/android/util/BinaryXmlTest.java
@@ -24,6 +24,8 @@
import static android.util.XmlTest.doVerifyWrite;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.os.PersistableBundle;
@@ -41,12 +43,15 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
@RunWith(AndroidJUnit4.class)
public class BinaryXmlTest {
+ private static final int MAX_UNSIGNED_SHORT = 65_535;
+
/**
* Verify that we can write and read large numbers of interned
* {@link String} values.
@@ -170,4 +175,49 @@
}
}
}
+
+ @Test
+ public void testAttributeBytes_BinaryDataOverflow() throws Exception {
+ final TypedXmlSerializer out = Xml.newBinarySerializer();
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ out.setOutput(os, StandardCharsets.UTF_8.name());
+
+ final byte[] testBytes = new byte[MAX_UNSIGNED_SHORT + 1];
+ assertThrows(IOException.class,
+ () -> out.attributeBytesHex(/* namespace */ null, /* name */ "attributeBytesHex",
+ testBytes));
+
+ assertThrows(IOException.class,
+ () -> out.attributeBytesBase64(/* namespace */ null, /* name */
+ "attributeBytesBase64", testBytes));
+ }
+
+ @Test
+ public void testAttributeBytesHex_MaximumBinaryData() throws Exception {
+ final TypedXmlSerializer out = Xml.newBinarySerializer();
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ out.setOutput(os, StandardCharsets.UTF_8.name());
+
+ final byte[] testBytes = new byte[MAX_UNSIGNED_SHORT];
+ try {
+ out.attributeBytesHex(/* namespace */ null, /* name */ "attributeBytesHex", testBytes);
+ } catch (Exception e) {
+ fail("testAttributeBytesHex fails with exception: " + e.toString());
+ }
+ }
+
+ @Test
+ public void testAttributeBytesBase64_MaximumBinaryData() throws Exception {
+ final TypedXmlSerializer out = Xml.newBinarySerializer();
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ out.setOutput(os, StandardCharsets.UTF_8.name());
+
+ final byte[] testBytes = new byte[MAX_UNSIGNED_SHORT];
+ try {
+ out.attributeBytesBase64(/* namespace */ null, /* name */ "attributeBytesBase64",
+ testBytes);
+ } catch (Exception e) {
+ fail("testAttributeBytesBase64 fails with exception: " + e.toString());
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index 07446e7..abe9c8e 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -164,7 +164,7 @@
mActivityRule.runOnUiThread(() -> {
mMovingView.setFrameContentVelocity(1f);
mMovingView.invalidate();
- runAfterDraw(() -> assertEquals(60f, mViewRoot.getLastPreferredFrameRate(), 0f));
+ runAfterDraw(() -> assertEquals(80f, mViewRoot.getLastPreferredFrameRate(), 0f));
});
waitForAfterDraw();
}
@@ -190,7 +190,7 @@
frameLayout.setFrameContentVelocity(1f);
mMovingView.offsetTopAndBottom(100);
frameLayout.invalidate();
- runAfterDraw(() -> assertEquals(60f, mViewRoot.getLastPreferredFrameRate(), 0f));
+ runAfterDraw(() -> assertEquals(80f, mViewRoot.getLastPreferredFrameRate(), 0f));
});
waitForAfterDraw();
}
@@ -435,7 +435,7 @@
runAfterDraw(() -> {
assertEquals(FRAME_RATE_CATEGORY_LOW,
mViewRoot.getLastPreferredFrameRateCategory());
- assertEquals(60f, mViewRoot.getLastPreferredFrameRate());
+ assertEquals(80f, mViewRoot.getLastPreferredFrameRate());
});
});
waitForAfterDraw();
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 08e695b..15f8c32 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -1,3 +1,5 @@
+# proto-file: build/make/tools/aconfig/aconfig_protos/protos/aconfig.proto
+
package: "com.android.wm.shell"
container: "system"
@@ -99,3 +101,13 @@
description: "Enable UI affordances to put other content into a bubble"
bug: "342245211"
}
+
+flag {
+ name: "only_reuse_bubbled_task_when_launched_from_bubble"
+ namespace: "multitasking"
+ description: "Allow reusing bubbled tasks for new activities only when launching from bubbles"
+ bug: "328229865"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index ee740fb..9114c7a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -27,6 +27,7 @@
import android.graphics.Rect
import android.graphics.RectF
import android.os.RemoteException
+import android.util.TimeUtils
import android.view.Choreographer
import android.view.Display
import android.view.IRemoteAnimationFinishedCallback
@@ -109,6 +110,7 @@
private val postCommitFlingSpring = SpringForce(SPRING_SCALE)
.setStiffness(SpringForce.STIFFNESS_LOW)
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+ protected var gestureProgress = 0f
/** Background color to be used during the animation, also see [getBackgroundColor] */
protected var customizedBackgroundColor = 0
@@ -212,6 +214,7 @@
private fun onGestureProgress(backEvent: BackEvent) {
val progress = gestureInterpolator.getInterpolation(backEvent.progress)
+ gestureProgress = progress
currentClosingRect.setInterpolatedRectF(startClosingRect, targetClosingRect, progress)
val yOffset = getYOffset(currentClosingRect, backEvent.touchY)
currentClosingRect.offset(0f, yOffset)
@@ -257,12 +260,16 @@
}
// kick off spring animation with the current velocity from the pre-commit phase, this
- // affects the scaling of the closing activity during post-commit
+ // affects the scaling of the closing and/or opening activity during post-commit
+ val startVelocity =
+ if (gestureProgress < 0.1f) -DEFAULT_FLING_VELOCITY else -velocity * SPRING_SCALE
val flingAnimation = SpringAnimation(postCommitFlingScale, SPRING_SCALE)
- .setStartVelocity(min(0f, -velocity * SPRING_SCALE))
+ .setStartVelocity(startVelocity.coerceIn(-MAX_FLING_VELOCITY, 0f))
.setStartValue(SPRING_SCALE)
.setSpring(postCommitFlingSpring)
flingAnimation.start()
+ // do an animation-frame immediately to prevent idle frame
+ flingAnimation.doAnimationFrame(choreographer.lastFrameTimeNanos / TimeUtils.NANOS_PER_MS)
val valueAnimator =
ValueAnimator.ofFloat(1f, 0f).setDuration(getPostCommitAnimationDuration())
@@ -292,6 +299,7 @@
enteringTarget?.let {
if (it.leash != null && it.leash.isValid) {
transaction.setCornerRadius(it.leash, 0f)
+ if (!triggerBack) transaction.setAlpha(it.leash, 0f)
it.leash.release()
}
enteringTarget = null
@@ -315,19 +323,22 @@
isLetterboxed = false
enteringHasSameLetterbox = false
lastPostCommitFlingScale = SPRING_SCALE
+ gestureProgress = 0f
}
protected fun applyTransform(
leash: SurfaceControl?,
rect: RectF,
alpha: Float,
- baseTransformation: Transformation? = null
+ baseTransformation: Transformation? = null,
+ flingMode: FlingMode = FlingMode.NO_FLING
) {
if (leash == null || !leash.isValid) return
tempRectF.set(rect)
- if (leash == closingTarget?.leash) {
- lastPostCommitFlingScale = (postCommitFlingScale.value / SPRING_SCALE).coerceIn(
- minimumValue = MAX_FLING_SCALE, maximumValue = lastPostCommitFlingScale
+ if (flingMode != FlingMode.NO_FLING) {
+ lastPostCommitFlingScale = min(
+ postCommitFlingScale.value / SPRING_SCALE,
+ if (flingMode == FlingMode.FLING_BOUNCE) 1f else lastPostCommitFlingScale
)
// apply an additional scale to the closing target to account for fling velocity
tempRectF.scaleCentered(lastPostCommitFlingScale)
@@ -529,14 +540,30 @@
private const val MAX_SCRIM_ALPHA_DARK = 0.8f
private const val MAX_SCRIM_ALPHA_LIGHT = 0.2f
private const val SPRING_SCALE = 100f
- private const val MAX_FLING_SCALE = 0.6f
+ private const val MAX_FLING_VELOCITY = 1000f
+ private const val DEFAULT_FLING_VELOCITY = 120f
+ }
+
+ enum class FlingMode {
+ NO_FLING,
+
+ /**
+ * This is used for the closing target in custom cross-activity back animations. When the
+ * back gesture is flung, the closing target shrinks a bit further with a spring motion.
+ */
+ FLING_SHRINK,
+
+ /**
+ * This is used for the closing and opening target in the default cross-activity back
+ * animation. When the back gesture is flung, the closing and opening targets shrink a
+ * bit further and then bounce back with a spring motion.
+ */
+ FLING_BOUNCE
}
}
// The target will loose focus when alpha == 0, so keep a minimum value for it.
-private fun keepMinimumAlpha(transAlpha: Float): Float {
- return max(transAlpha.toDouble(), 0.005).toFloat()
-}
+private fun keepMinimumAlpha(transAlpha: Float) = max(transAlpha, 0.005f)
private fun isDarkMode(context: Context): Boolean {
return context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
index c4aafea..c738ce5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
@@ -57,7 +57,6 @@
private var enterAnimation: Animation? = null
private var closeAnimation: Animation? = null
private val transformation = Transformation()
- private var gestureProgress = 0f
override val allowEnteringYShift = false
@@ -105,7 +104,6 @@
}
override fun getPreCommitEnteringBaseTransformation(progress: Float): Transformation {
- gestureProgress = progress
transformation.clear()
enterAnimation!!.getTransformationAt(progress * PRE_COMMIT_MAX_PROGRESS, transformation)
return transformation
@@ -134,7 +132,13 @@
if (closingTarget == null || enteringTarget == null) return
val closingProgress = closeAnimation!!.getPostCommitProgress(linearProgress)
- applyTransform(closingTarget!!.leash, currentClosingRect, closingProgress, closeAnimation!!)
+ applyTransform(
+ closingTarget!!.leash,
+ currentClosingRect,
+ closingProgress,
+ closeAnimation!!,
+ FlingMode.FLING_SHRINK
+ )
val enteringProgress = MathUtils.lerp(
gestureProgress * PRE_COMMIT_MAX_PROGRESS,
1f,
@@ -144,7 +148,8 @@
enteringTarget!!.leash,
currentEnteringRect,
enteringProgress,
- enterAnimation!!
+ enterAnimation!!,
+ FlingMode.NO_FLING
)
applyTransaction()
}
@@ -153,11 +158,12 @@
leash: SurfaceControl,
rect: RectF,
progress: Float,
- animation: Animation
+ animation: Animation,
+ flingMode: FlingMode
) {
transformation.clear()
animation.getTransformationAt(progress, transformation)
- applyTransform(leash, rect, transformation.alpha, transformation)
+ applyTransform(leash, rect, transformation.alpha, transformation, flingMode)
}
override fun finishAnimation() {
@@ -166,7 +172,6 @@
enterAnimation?.reset()
enterAnimation = null
transformation.clear()
- gestureProgress = 0f
super.finishAnimation()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
index 44752fe..3b5eb36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
@@ -43,7 +43,7 @@
Choreographer.getInstance()
) {
- private val postCommitInterpolator = Interpolators.FAST_OUT_SLOW_IN
+ private val postCommitInterpolator = Interpolators.EMPHASIZED
private val enteringStartOffset =
context.resources.getDimension(R.dimen.cross_activity_back_entering_start_offset)
override val allowEnteringYShift = true
@@ -87,17 +87,27 @@
override fun onPostCommitProgress(linearProgress: Float) {
super.onPostCommitProgress(linearProgress)
- val closingAlpha = max(1f - linearProgress * 2, 0f)
+ val closingAlpha = max(1f - linearProgress * 5, 0f)
val progress = postCommitInterpolator.getInterpolation(linearProgress)
currentClosingRect.setInterpolatedRectF(startClosingRect, targetClosingRect, progress)
- applyTransform(closingTarget?.leash, currentClosingRect, closingAlpha)
+ applyTransform(
+ closingTarget?.leash,
+ currentClosingRect,
+ closingAlpha,
+ flingMode = FlingMode.FLING_BOUNCE
+ )
currentEnteringRect.setInterpolatedRectF(startEnteringRect, targetEnteringRect, progress)
- applyTransform(enteringTarget?.leash, currentEnteringRect, 1f)
+ applyTransform(
+ enteringTarget?.leash,
+ currentEnteringRect,
+ 1f,
+ flingMode = FlingMode.FLING_BOUNCE
+ )
applyTransaction()
}
companion object {
- private const val POST_COMMIT_DURATION = 300L
+ private const val POST_COMMIT_DURATION = 450L
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index c79eef7e..cd478e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -143,6 +143,10 @@
}
public static boolean handles(TransitionInfo info) {
+ // There is no animation for screen-wake unless we are immediately unlocking.
+ if (info.getType() == WindowManager.TRANSIT_WAKE && !info.isKeyguardGoingAway()) {
+ return false;
+ }
return (info.getFlags() & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0;
}
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 5ce990f..7b7ccf5 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -48,7 +48,6 @@
"libgui",
"libui",
"libinput",
- "libnativewindow",
],
header_libs: [
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
index 9c8ec3b..3022fa0 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
@@ -27,6 +27,7 @@
import android.credentials.selection.Entry
import android.credentials.selection.GetCredentialProviderData
import android.graphics.drawable.Drawable
+import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import androidx.activity.result.IntentSenderRequest
@@ -227,26 +228,31 @@
* and get flows utilize slice params; includes the final '.' before the name of the type (e.g.
* androidx.credentials.provider.credentialEntry.SLICE_HINT_ALLOWED_AUTHENTICATORS must have
* 'hintPrefix' up to "androidx.credentials.provider.credentialEntry.")
- * // TODO(b/326243754) : Presently, due to dependencies, the opId bit is parsed but is never
- * // expected to be used. When it is added, it should be lightly validated.
*/
fun retrieveEntryBiometricRequest(
entry: Entry,
- hintPrefix: String,
+ hintPrefix: String
): BiometricRequestInfo? {
- // TODO(b/326243754) : When available, use the official jetpack structured type
- val allowedAuthenticators: Int? = entry.slice.items.firstOrNull {
- it.hasHint(hintPrefix + "SLICE_HINT_ALLOWED_AUTHENTICATORS")
- }?.int
+ // TODO(b/326243754) : When available, use the official jetpack structured typLo
+ val biometricPromptDataBundleKey = "SLICE_HINT_BIOMETRIC_PROMPT_DATA"
+ val biometricPromptDataBundle: Bundle = entry.slice.items.firstOrNull {
+ it.hasHint(hintPrefix + biometricPromptDataBundleKey)
+ }?.bundle ?: return null
+
+ val allowedAuthConstantKey = "androidx.credentials.provider.BUNDLE_HINT_ALLOWED_AUTHENTICATORS"
+ val cryptoOpIdKey = "androidx.credentials.provider.BUNDLE_HINT_CRYPTO_OP_ID"
+
+ if (!biometricPromptDataBundle.containsKey(allowedAuthConstantKey)) {
+ return null
+ }
+
+ val allowedAuthenticators: Int = biometricPromptDataBundle.getInt(allowedAuthConstantKey)
// This is optional and does not affect validating the biometric flow in any case
- val opId: Int? = entry.slice.items.firstOrNull {
- it.hasHint(hintPrefix + "SLICE_HINT_CRYPTO_OP_ID")
- }?.int
- if (allowedAuthenticators != null) {
- return BiometricRequestInfo(opId = opId, allowedAuthenticators = allowedAuthenticators)
- }
- return null
+ val opId: Long? = if (biometricPromptDataBundle.containsKey(cryptoOpIdKey))
+ biometricPromptDataBundle.getLong(cryptoOpIdKey) else null
+
+ return BiometricRequestInfo(opId = opId, allowedAuthenticators = allowedAuthenticators)
}
val Slice.credentialEntry: CredentialEntry?
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/BiometricRequestInfo.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/BiometricRequestInfo.kt
index 486cfe7..fe4bead 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/BiometricRequestInfo.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/BiometricRequestInfo.kt
@@ -23,6 +23,6 @@
* null.
*/
data class BiometricRequestInfo(
- val opId: Int? = null,
+ val opId: Long? = null,
val allowedAuthenticators: Int
)
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 7bc25ed..894d5ef 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -135,16 +135,18 @@
Log.w(Constants.LOG_TAG, "Unexpected biometric result exists when " +
"autoSelect is preferred.")
}
- // TODO(b/333445754) : Decide whether to propagate info on prompt launch
+ // TODO(b/333445754) : Change the fm option to false in qpr after discussion
if (biometricState.biometricResult != null) {
entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_RESULT,
biometricState.biometricResult.biometricAuthenticationResult
.authenticationType)
+ entryIntent?.putExtra(Constants.BIOMETRIC_FRAMEWORK_OPTION, true)
} else if (biometricState.biometricError != null){
entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_ERROR_CODE,
biometricState.biometricError.errorCode)
entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_ERROR_MESSAGE,
biometricState.biometricError.errorMessage)
+ entryIntent?.putExtra(Constants.BIOMETRIC_FRAMEWORK_OPTION, true)
}
}
val intentSenderRequest = IntentSenderRequest.Builder(pendingIntent)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
index 373b3e8..c35721c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
@@ -230,7 +230,7 @@
val cryptoOpId = getCryptoOpId(biometricDisplayInfo)
if (cryptoOpId != null) {
biometricPrompt.authenticate(
- BiometricPrompt.CryptoObject(cryptoOpId.toLong()),
+ BiometricPrompt.CryptoObject(cryptoOpId),
cancellationSignal, executor, callback)
} else {
biometricPrompt.authenticate(cancellationSignal, executor, callback)
@@ -243,7 +243,7 @@
return true
}
-private fun getCryptoOpId(biometricDisplayInfo: BiometricDisplayInfo): Int? {
+private fun getCryptoOpId(biometricDisplayInfo: BiometricDisplayInfo): Long? {
return biometricDisplayInfo.biometricRequestInfo.opId
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
index 3c80113..cb089ad 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
@@ -22,9 +22,12 @@
const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
"androidx.credentials.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED"
const val IS_AUTO_SELECTED_KEY = "IS_AUTO_SELECTED"
- // TODO(b/333445772) : Qualify error codes fully for propagation
- const val BIOMETRIC_AUTH_RESULT = "BIOMETRIC_AUTH_RESULT"
- const val BIOMETRIC_AUTH_ERROR_CODE = "BIOMETRIC_AUTH_ERROR_CODE"
- const val BIOMETRIC_AUTH_ERROR_MESSAGE = "BIOMETRIC_AUTH_ERROR_MESSAGE"
+ const val BIOMETRIC_AUTH_RESULT = "androidx.credentials.provider.BIOMETRIC_AUTH_RESULT"
+ const val BIOMETRIC_AUTH_ERROR_CODE =
+ "androidx.credentials.provider.BIOMETRIC_AUTH_ERROR_CODE"
+ const val BIOMETRIC_AUTH_ERROR_MESSAGE =
+ "androidx.credentials.provider.BIOMETRIC_AUTH_ERROR_MESSAGE"
+ const val BIOMETRIC_FRAMEWORK_OPTION =
+ "androidx.credentials.provider.BIOMETRIC_FRAMEWORK_OPTION"
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt
index 39f2fce..dac25fa 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt
@@ -17,7 +17,6 @@
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.UiEventLogger.UiEventEnum.RESERVE_NEW_UI_EVENT_ID
enum class CreateCredentialEvent(private val id: Int) : UiEventLogger.UiEventEnum {
@@ -56,7 +55,7 @@
CREDMAN_CREATE_CRED_MORE_ABOUT_PASSKEYS_INTRO(1328),
@UiEvent(doc = "The single tap biometric flow is launched.")
- CREDMAN_CREATE_CRED_BIOMETRIC_FLOW_LAUNCHED(RESERVE_NEW_UI_EVENT_ID);
+ CREDMAN_CREATE_CRED_BIOMETRIC_FLOW_LAUNCHED(1800);
override fun getId(): Int {
return this.id
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt
index 89fd72c..8870f28 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt
@@ -17,7 +17,6 @@
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.UiEventLogger.UiEventEnum.RESERVE_NEW_UI_EVENT_ID
enum class GetCredentialEvent(private val id: Int) : UiEventLogger.UiEventEnum {
@@ -58,7 +57,7 @@
CREDMAN_GET_CRED_ALL_SIGN_IN_OPTION_CARD(1342),
@UiEvent(doc = "The single tap biometric flow is launched.")
- CREDMAN_GET_CRED_BIOMETRIC_FLOW_LAUNCHED(RESERVE_NEW_UI_EVENT_ID);
+ CREDMAN_GET_CRED_BIOMETRIC_FLOW_LAUNCHED(1801);
override fun getId(): Int {
return this.id
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index 59a511d..10e8246 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -311,18 +311,18 @@
broadcastIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
- try {
- // Delay committing the session by 100ms to fix a UI glitch while displaying the
- // Update-Owner change dialog on top of the Installing dialog
- new Handler(Looper.getMainLooper()).postDelayed(() -> {
+ // Delay committing the session by 100ms to fix a UI glitch while displaying the
+ // Update-Owner change dialog on top of the Installing dialog
+ new Handler(Looper.getMainLooper()).postDelayed(() -> {
+ try {
session.commit(pendingIntent.getIntentSender());
- }, 100);
- } catch (Exception e) {
- Log.e(LOG_TAG, "Cannot install package: ", e);
- launchFailure(PackageInstaller.STATUS_FAILURE,
- PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
- return;
- }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Cannot install package: ", e);
+ launchFailure(PackageInstaller.STATUS_FAILURE,
+ PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
+ return;
+ }
+ }, 100);
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
} else {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index 3fea599..379dfe3 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -125,13 +125,14 @@
-1, callingUid) == PackageManager.PERMISSION_GRANTED;
boolean isSystemDownloadsProvider = PackageUtil.getSystemDownloadsProviderInfo(
mPackageManager, callingUid) != null;
- boolean isTrustedSource = false;
- if (sourceInfo != null && sourceInfo.isPrivilegedApp()) {
- isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false) || (
- callingUid != Process.INVALID_UID && checkPermission(
- Manifest.permission.INSTALL_PACKAGES, -1 /* pid */, callingUid)
- == PackageManager.PERMISSION_GRANTED);
- }
+
+ boolean isPrivilegedAndKnown = (sourceInfo != null && sourceInfo.isPrivilegedApp()) &&
+ intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
+ boolean isInstallPkgPermissionGranted =
+ checkPermission(Manifest.permission.INSTALL_PACKAGES, /* pid= */ -1, callingUid)
+ == PackageManager.PERMISSION_GRANTED;
+
+ boolean isTrustedSource = isPrivilegedAndKnown || isInstallPkgPermissionGranted;
if (!isTrustedSource && !isSystemDownloadsProvider && !isDocumentsManager
&& callingUid != Process.INVALID_UID) {
@@ -154,7 +155,7 @@
mAbortInstall = true;
}
- checkDevicePolicyRestrictions();
+ checkDevicePolicyRestrictions(isTrustedSource);
final String installerPackageNameFromIntent = getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
@@ -304,12 +305,17 @@
return callingUid == installerUid;
}
- private void checkDevicePolicyRestrictions() {
- final String[] restrictions = new String[] {
- UserManager.DISALLOW_INSTALL_APPS,
- UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
- UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
- };
+ private void checkDevicePolicyRestrictions(boolean isTrustedSource) {
+ String[] restrictions;
+ if(isTrustedSource) {
+ restrictions = new String[] { UserManager.DISALLOW_INSTALL_APPS };
+ } else {
+ restrictions = new String[] {
+ UserManager.DISALLOW_INSTALL_APPS,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
+ };
+ }
final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
for (String restriction : restrictions) {
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
index cec2992..cf645f7 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
@@ -41,6 +41,9 @@
static {
HashMap<String, Integer> map = new HashMap<>();
map.put(
+ ".grey200",
+ R.color.settingslib_color_grey800);
+ map.put(
".grey600",
R.color.settingslib_color_grey400);
map.put(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
index e9fed8e..ea4480f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
@@ -49,6 +49,7 @@
object LottieColorUtils {
private val DARK_TO_LIGHT_THEME_COLOR_MAP = mapOf(
+ ".grey200" to R.color.settingslib_color_grey800,
".grey600" to R.color.settingslib_color_grey400,
".grey800" to R.color.settingslib_color_grey300,
".grey900" to R.color.settingslib_color_grey50,
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt
index 71072a5..d91c7e6 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt
@@ -16,9 +16,9 @@
package com.android.settingslib.spa.widget.ui
-import android.content.ClipData
-import android.content.ClipboardManager
import android.content.Context
+import androidx.compose.ui.platform.ClipboardManager
+import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.longClick
@@ -63,9 +63,9 @@
@Test
fun onCopy_saveToClipboard() {
- val clipboardManager = context.getSystemService(ClipboardManager::class.java)!!
- clipboardManager.setPrimaryClip(ClipData.newPlainText("", ""))
+ var clipboardManager: ClipboardManager? = null
composeTestRule.setContent {
+ clipboardManager = LocalClipboardManager.current
CopyableBody(TEXT)
}
@@ -74,7 +74,7 @@
}
composeTestRule.onNodeWithText(context.getString(android.R.string.copy)).performClick()
- assertThat(clipboardManager.primaryClip!!.getItemAt(0).text.toString()).isEqualTo(TEXT)
+ assertThat(clipboardManager?.getText()?.text).isEqualTo(TEXT)
}
private companion object {
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index f83928d..03c2a83 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -418,6 +418,7 @@
VALIDATORS.put(Global.Wearable.CHARGING_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.BEDTIME_MODE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.BEDTIME_HARD_MODE, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.Wearable.VIBRATE_FOR_ACTIVE_UNLOCK, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.DYNAMIC_COLOR_THEME_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.SCREENSHOT_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.UPGRADE_DATA_MIGRATION_STATUS,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 04922d6..c8992c3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -64,6 +64,7 @@
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.InputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
@@ -85,13 +86,13 @@
// FOR ACONFIGD TEST MISSION AND ROLLOUT
import java.io.DataInputStream;
import java.io.DataOutputStream;
-import android.net.LocalSocketAddress;
-import android.net.LocalSocket;
import android.util.proto.ProtoInputStream;
import android.aconfigd.Aconfigd.StorageRequestMessage;
import android.aconfigd.Aconfigd.StorageRequestMessages;
import android.aconfigd.Aconfigd.StorageReturnMessage;
import android.aconfigd.Aconfigd.StorageReturnMessages;
+import android.aconfigd.AconfigdClientSocket;
+import android.aconfigd.AconfigdFlagInfo;
import android.aconfigd.AconfigdJavaUtils;
import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
/**
@@ -265,6 +266,10 @@
@NonNull
private Map<String, Map<String, String>> mNamespaceDefaults;
+ // TOBO(b/312444587): remove the comparison logic after Test Mission 2.
+ @NonNull
+ private Map<String, AconfigdFlagInfo> mAconfigDefaultFlags;
+
public static final int SETTINGS_TYPE_GLOBAL = 0;
public static final int SETTINGS_TYPE_SYSTEM = 1;
public static final int SETTINGS_TYPE_SECURE = 2;
@@ -334,8 +339,13 @@
+ settingTypeToString(getTypeFromKey(key)) + "]";
}
- public SettingsState(Context context, Object lock, File file, int key,
- int maxBytesPerAppPackage, Looper looper) {
+ public SettingsState(
+ Context context,
+ Object lock,
+ File file,
+ int key,
+ int maxBytesPerAppPackage,
+ Looper looper) {
// It is important that we use the same lock as the settings provider
// to ensure multiple mutations on this state are atomically persisted
// as the async persistence should be blocked while we make changes.
@@ -353,12 +363,15 @@
mPackageToMemoryUsage = null;
}
- mHistoricalOperations = Build.IS_DEBUGGABLE
- ? new ArrayList<>(HISTORICAL_OPERATION_COUNT) : null;
+ mHistoricalOperations =
+ Build.IS_DEBUGGABLE ? new ArrayList<>(HISTORICAL_OPERATION_COUNT) : null;
mNamespaceDefaults = new HashMap<>();
+ mAconfigDefaultFlags = new HashMap<>();
ProtoOutputStream requests = null;
+ Map<String, AconfigdFlagInfo> aconfigFlagMap = new HashMap<>();
+
synchronized (mLock) {
readStateSyncLocked();
@@ -375,39 +388,114 @@
}
}
+ if (enableAconfigStorageDaemon()) {
+ if (isConfigSettingsKey(mKey)) {
+ aconfigFlagMap = getAllAconfigFlagsFromSettings();
+ }
+ }
+
if (isConfigSettingsKey(mKey)) {
- requests = handleBulkSyncToNewStorage();
+ requests = handleBulkSyncToNewStorage(aconfigFlagMap);
}
}
- if (requests != null) {
- LocalSocket client = new LocalSocket();
- try{
- client.connect(new LocalSocketAddress(
- "aconfigd", LocalSocketAddress.Namespace.RESERVED));
- Slog.d(LOG_TAG, "connected to aconfigd socket");
- } catch (IOException ioe) {
- Slog.e(LOG_TAG, "failed to connect to aconfigd socket", ioe);
- return;
+ if (enableAconfigStorageDaemon()) {
+ if (isConfigSettingsKey(mKey)){
+ AconfigdClientSocket localSocket = AconfigdJavaUtils.getAconfigdClientSocket();
+ if (requests != null) {
+ InputStream res = localSocket.send(requests.getBytes());
+ if (res == null) {
+ Slog.w(LOG_TAG, "Bulk sync request to acongid failed.");
+ }
+ }
+ // TOBO(b/312444587): remove the comparison logic after Test Mission 2.
+ if (mSettings.get("aconfigd_marker/bulk_synced").value.equals("true")
+ && requests == null) {
+ Map<String, AconfigdFlagInfo> aconfigdFlagMap =
+ AconfigdJavaUtils.listFlagsValueInNewStorage(localSocket);
+ compareFlagValueInNewStorage(
+ aconfigFlagMap,
+ mAconfigDefaultFlags,
+ aconfigdFlagMap);
+ }
}
- AconfigdJavaUtils.sendAconfigdRequests(client, requests);
}
}
- // TODO(b/341764371): migrate aconfig flag push to GMS core
- public static class FlagOverrideToSync {
- public String packageName;
- public String flagName;
- public String flagValue;
- public boolean isLocal;
+ // TOBO(b/312444587): remove the comparison logic after Test Mission 2.
+ public int compareFlagValueInNewStorage(
+ Map<String, AconfigdFlagInfo> settingFlagMap,
+ Map<String, AconfigdFlagInfo> defaultFlagMap,
+ Map<String, AconfigdFlagInfo> aconfigdFlagMap) {
+
+ // Get all defaults from the default map. The mSettings may not contain
+ // all flags, since it only contains updated flags.
+ int diffNum = 0;
+ for (Map.Entry<String, AconfigdFlagInfo> entry : defaultFlagMap.entrySet()) {
+ String key = entry.getKey();
+ AconfigdFlagInfo flag = entry.getValue();
+ if (settingFlagMap.containsKey(key)) {
+ flag.merge(settingFlagMap.get(key));
+ }
+
+ AconfigdFlagInfo aconfigdFlag = aconfigdFlagMap.get(key);
+ if (aconfigdFlag == null) {
+ Slog.w(LOG_TAG, String.format("Flag %s is missing from aconfigd", key));
+ diffNum++;
+ continue;
+ }
+ String diff = flag.dumpDiff(aconfigdFlag);
+ if (!diff.isEmpty()) {
+ Slog.w(
+ LOG_TAG,
+ String.format(
+ "Flag %s is different in Settings and aconfig: %s", key, diff));
+ diffNum++;
+ }
+ }
+
+ for (String key : aconfigdFlagMap.keySet()) {
+ if (defaultFlagMap.containsKey(key)) continue;
+ Slog.w(LOG_TAG, String.format("Flag %s is missing from Settings", key));
+ diffNum++;
+ }
+
+ if (diffNum == 0) {
+ Slog.i(LOG_TAG, "Settings and new storage have same flags.");
+ }
+ return diffNum;
+ }
+
+ @GuardedBy("mLock")
+ public Map<String, AconfigdFlagInfo> getAllAconfigFlagsFromSettings() {
+ Map<String, AconfigdFlagInfo> ret = new HashMap<>();
+ int numSettings = mSettings.size();
+ int num_requests = 0;
+ for (int i = 0; i < numSettings; i++) {
+ String name = mSettings.keyAt(i);
+ Setting setting = mSettings.valueAt(i);
+ AconfigdFlagInfo flag =
+ getFlagOverrideToSync(name, setting.getValue());
+ if (flag == null) {
+ continue;
+ }
+ String fullFlagName = flag.getFullFlagName();
+ AconfigdFlagInfo prev = ret.putIfAbsent(fullFlagName,flag);
+ if (prev != null) {
+ prev.merge(flag);
+ }
+ ++num_requests;
+ }
+ Slog.i(LOG_TAG, num_requests + " flag override requests created");
+ return ret;
}
// TODO(b/341764371): migrate aconfig flag push to GMS core
@VisibleForTesting
@GuardedBy("mLock")
- public FlagOverrideToSync getFlagOverrideToSync(String name, String value) {
+ public AconfigdFlagInfo getFlagOverrideToSync(String name, String value) {
int slashIdx = name.indexOf("/");
- if (slashIdx <= 0 || slashIdx >= name.length()-1) {
+ if (slashIdx <= 0 || slashIdx >= name.length() - 1) {
Slog.e(LOG_TAG, "invalid flag name " + name);
return null;
}
@@ -430,8 +518,9 @@
}
String aconfigName = namespace + "/" + fullFlagName;
- boolean isAconfig = mNamespaceDefaults.containsKey(namespace)
- && mNamespaceDefaults.get(namespace).containsKey(aconfigName);
+ boolean isAconfig =
+ mNamespaceDefaults.containsKey(namespace)
+ && mNamespaceDefaults.get(namespace).containsKey(aconfigName);
if (!isAconfig) {
return null;
}
@@ -443,25 +532,30 @@
return null;
}
- FlagOverrideToSync flag = new FlagOverrideToSync();
- flag.packageName = fullFlagName.substring(0, dotIdx);
- flag.flagName = fullFlagName.substring(dotIdx + 1);
- flag.isLocal = isLocal;
- flag.flagValue = value;
- return flag;
+ AconfigdFlagInfo.Builder builder = AconfigdFlagInfo.newBuilder()
+ .setPackageName(fullFlagName.substring(0, dotIdx))
+ .setFlagName(fullFlagName.substring(dotIdx + 1))
+ .setDefaultFlagValue(mNamespaceDefaults.get(namespace).get(aconfigName));
+
+ if (isLocal) {
+ builder.setHasLocalOverride(isLocal).setBootFlagValue(value).setLocalFlagValue(value);
+ } else {
+ builder.setHasServerOverride(true).setServerFlagValue(value).setBootFlagValue(value);
+ }
+ return builder.build();
}
// TODO(b/341764371): migrate aconfig flag push to GMS core
@VisibleForTesting
@GuardedBy("mLock")
- public ProtoOutputStream handleBulkSyncToNewStorage() {
+ public ProtoOutputStream handleBulkSyncToNewStorage(
+ Map<String, AconfigdFlagInfo> aconfigFlagMap) {
// get marker or add marker if it does not exist
final String bulkSyncMarkerName = new String("aconfigd_marker/bulk_synced");
Setting markerSetting = mSettings.get(bulkSyncMarkerName);
if (markerSetting == null) {
- markerSetting = new Setting(
- bulkSyncMarkerName, "false", false, "aconfig", "aconfig");
+ markerSetting = new Setting(bulkSyncMarkerName, "false", false, "aconfig", "aconfig");
mSettings.put(bulkSyncMarkerName, markerSetting);
}
@@ -479,24 +573,19 @@
AconfigdJavaUtils.writeResetStorageRequest(requests);
// loop over all settings and add flag override requests
- final int numSettings = mSettings.size();
- int num_requests = 0;
- for (int i = 0; i < numSettings; i++) {
- String name = mSettings.keyAt(i);
- Setting setting = mSettings.valueAt(i);
- FlagOverrideToSync flag =
- getFlagOverrideToSync(name, setting.getValue());
- if (flag == null) {
- continue;
- }
- ++num_requests;
+ for (AconfigdFlagInfo flag : aconfigFlagMap.values()) {
+ String value =
+ flag.getHasLocalOverride()
+ ? flag.getLocalFlagValue()
+ : flag.getServerFlagValue();
AconfigdJavaUtils.writeFlagOverrideRequest(
- requests, flag.packageName, flag.flagName, flag.flagValue,
- flag.isLocal);
+ requests,
+ flag.getPackageName(),
+ flag.getFlagName(),
+ value,
+ flag.getHasLocalOverride());
}
- Slog.i(LOG_TAG, num_requests + " flag override requests created");
-
// mark sync has been done
markerSetting.value = "true";
scheduleWriteIfNeededLocked();
@@ -513,14 +602,14 @@
return null;
}
}
-
}
@GuardedBy("mLock")
private void loadAconfigDefaultValuesLocked(List<String> filePaths) {
for (String fileName : filePaths) {
try (FileInputStream inputStream = new FileInputStream(fileName)) {
- loadAconfigDefaultValues(inputStream.readAllBytes(), mNamespaceDefaults);
+ loadAconfigDefaultValues(
+ inputStream.readAllBytes(), mNamespaceDefaults, mAconfigDefaultFlags);
} catch (IOException e) {
Slog.e(LOG_TAG, "failed to read protobuf", e);
}
@@ -566,21 +655,30 @@
@VisibleForTesting
@GuardedBy("mLock")
- public static void loadAconfigDefaultValues(byte[] fileContents,
- @NonNull Map<String, Map<String, String>> defaultMap) {
+ public static void loadAconfigDefaultValues(
+ byte[] fileContents,
+ @NonNull Map<String, Map<String, String>> defaultMap,
+ @NonNull Map<String, AconfigdFlagInfo> flagInfoDefault) {
try {
- parsed_flags parsedFlags =
- parsed_flags.parseFrom(fileContents);
+ parsed_flags parsedFlags = parsed_flags.parseFrom(fileContents);
for (parsed_flag flag : parsedFlags.getParsedFlagList()) {
if (!defaultMap.containsKey(flag.getNamespace())) {
Map<String, String> defaults = new HashMap<>();
defaultMap.put(flag.getNamespace(), defaults);
}
- String flagName = flag.getNamespace()
- + "/" + flag.getPackage() + "." + flag.getName();
- String flagValue = flag.getState() == flag_state.ENABLED
- ? "true" : "false";
+ String fullFlagName = flag.getPackage() + "." + flag.getName();
+ String flagName = flag.getNamespace() + "/" + fullFlagName;
+ String flagValue = flag.getState() == flag_state.ENABLED ? "true" : "false";
defaultMap.get(flag.getNamespace()).put(flagName, flagValue);
+ if (!flagInfoDefault.containsKey(fullFlagName)) {
+ flagInfoDefault.put(
+ fullFlagName,
+ AconfigdFlagInfo.newBuilder()
+ .setPackageName(flag.getPackage())
+ .setFlagName(flag.getName())
+ .setDefaultFlagValue(flagValue)
+ .build());
+ }
}
} catch (IOException e) {
Slog.e(LOG_TAG, "failed to parse protobuf", e);
@@ -1646,7 +1744,6 @@
}
}
}
-
mSettings.put(name, new Setting(name, value, defaultValue, packageName, tag,
fromSystem, id, isPreservedInRestore));
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 473955f..4ec170d 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -619,6 +619,7 @@
Settings.Global.Wearable.COOLDOWN_MODE_ON,
Settings.Global.Wearable.BEDTIME_MODE,
Settings.Global.Wearable.BEDTIME_HARD_MODE,
+ Settings.Global.Wearable.VIBRATE_FOR_ACTIVE_UNLOCK,
Settings.Global.Wearable.LOCK_SCREEN_STATE,
Settings.Global.Wearable.DISABLE_AOD_WHILE_PLUGGED,
Settings.Global.Wearable.NETWORK_LOCATION_OPT_IN,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 244c8c4..256b999 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -24,13 +24,13 @@
import android.aconfig.Aconfig;
import android.aconfig.Aconfig.parsed_flag;
import android.aconfig.Aconfig.parsed_flags;
+import android.aconfigd.AconfigdFlagInfo;
import android.os.Looper;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
-import com.android.providers.settings.SettingsState.FlagOverrideToSync;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -145,16 +145,32 @@
.setState(Aconfig.flag_state.ENABLED)
.setPermission(Aconfig.flag_permission.READ_WRITE))
.build();
+
+ AconfigdFlagInfo flag1 = AconfigdFlagInfo.newBuilder()
+ .setPackageName("com.android.flags")
+ .setFlagName("flag1")
+ .setDefaultFlagValue("false")
+ .build();
+ AconfigdFlagInfo flag2 = AconfigdFlagInfo.newBuilder()
+ .setPackageName("com.android.flags")
+ .setFlagName("flag2")
+ .setDefaultFlagValue("true")
+ .build();
+ Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>();
synchronized (lock) {
Map<String, Map<String, String>> defaults = new HashMap<>();
- settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
+ settingsState.loadAconfigDefaultValues(
+ flags.toByteArray(), defaults, flagInfoDefault);
Map<String, String> namespaceDefaults = defaults.get("test_namespace");
assertEquals(2, namespaceDefaults.keySet().size());
assertEquals("false", namespaceDefaults.get("test_namespace/com.android.flags.flag1"));
assertEquals("true", namespaceDefaults.get("test_namespace/com.android.flags.flag2"));
}
+
+ assertEquals(flag1, flagInfoDefault.get(flag1.getFullFlagName()));
+ assertEquals(flag2, flagInfoDefault.get(flag2.getFullFlagName()));
}
@Test
@@ -165,6 +181,8 @@
InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+ Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>();
+
parsed_flags flags = parsed_flags
.newBuilder()
.addParsedFlag(parsed_flag
@@ -177,7 +195,8 @@
synchronized (lock) {
Map<String, Map<String, String>> defaults = new HashMap<>();
- settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
+ settingsState.loadAconfigDefaultValues(
+ flags.toByteArray(), defaults, flagInfoDefault);
Map<String, String> namespaceDefaults = defaults.get("test_namespace");
assertEquals(null, namespaceDefaults);
@@ -204,10 +223,12 @@
.setState(Aconfig.flag_state.DISABLED)
.setPermission(Aconfig.flag_permission.READ_WRITE))
.build();
+ Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>();
synchronized (lock) {
Map<String, Map<String, String>> defaults = new HashMap<>();
- settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
+ settingsState.loadAconfigDefaultValues(
+ flags.toByteArray(), defaults, flagInfoDefault);
settingsState.addAconfigDefaultValuesFromMap(defaults);
settingsState.insertSettingLocked("test_namespace/com.android.flags.flag5",
@@ -238,8 +259,10 @@
@Test
public void testInvalidAconfigProtoDoesNotCrash() {
Map<String, Map<String, String>> defaults = new HashMap<>();
+ Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>();
SettingsState settingsState = getSettingStateObject();
- settingsState.loadAconfigDefaultValues("invalid protobuf".getBytes(), defaults);
+ settingsState.loadAconfigDefaultValues(
+ "invalid protobuf".getBytes(), defaults, flagInfoDefault);
}
@Test
@@ -759,6 +782,8 @@
Map<String, String> keyValues =
Map.of("test_namespace/com.android.flags.flag3", "true");
+ Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>();
+
parsed_flags flags = parsed_flags
.newBuilder()
.addParsedFlag(parsed_flag
@@ -774,7 +799,8 @@
synchronized (mLock) {
settingsState.loadAconfigDefaultValues(
- flags.toByteArray(), settingsState.getAconfigDefaultValues());
+ flags.toByteArray(),
+ settingsState.getAconfigDefaultValues(), flagInfoDefault);
List<String> updates =
settingsState.setSettingsLocked("test_namespace/", keyValues, packageName);
assertEquals(1, updates.size());
@@ -840,10 +866,13 @@
.setState(Aconfig.flag_state.DISABLED)
.setPermission(Aconfig.flag_permission.READ_WRITE))
.build();
+ Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>();
synchronized (mLock) {
settingsState.loadAconfigDefaultValues(
- flags.toByteArray(), settingsState.getAconfigDefaultValues());
+ flags.toByteArray(),
+ settingsState.getAconfigDefaultValues(),
+ flagInfoDefault);
List<String> updates =
settingsState.setSettingsLocked("test_namespace/", keyValues, packageName);
assertEquals(3, updates.size());
@@ -973,10 +1002,12 @@
.setState(Aconfig.flag_state.DISABLED)
.setPermission(Aconfig.flag_permission.READ_WRITE))
.build();
+ Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>();
synchronized (lock) {
Map<String, Map<String, String>> defaults = new HashMap<>();
- settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
+ settingsState.loadAconfigDefaultValues(
+ flags.toByteArray(), defaults, flagInfoDefault);
Map<String, String> namespaceDefaults = defaults.get("test_namespace");
assertEquals(1, namespaceDefaults.keySet().size());
settingsState.addAconfigDefaultValuesFromMap(defaults);
@@ -991,22 +1022,28 @@
"some_namespace/some_flag", "false") == null);
// server override
- FlagOverrideToSync flag = settingsState.getFlagOverrideToSync(
+ AconfigdFlagInfo flag = settingsState.getFlagOverrideToSync(
"test_namespace/com.android.flags.flag1", "false");
assertTrue(flag != null);
- assertEquals(flag.packageName, "com.android.flags");
- assertEquals(flag.flagName, "flag1");
- assertEquals(flag.flagValue, "false");
- assertEquals(flag.isLocal, false);
+ assertEquals(flag.getPackageName(), "com.android.flags");
+ assertEquals(flag.getFlagName(), "flag1");
+ assertEquals("false", flag.getBootFlagValue());
+ assertEquals("false", flag.getServerFlagValue());
+ assertFalse(flag.getHasLocalOverride());
+ assertNull(flag.getLocalFlagValue());
+ assertEquals("false", flag.getDefaultFlagValue());
// local override
flag = settingsState.getFlagOverrideToSync(
"device_config_overrides/test_namespace:com.android.flags.flag1", "false");
assertTrue(flag != null);
- assertEquals(flag.packageName, "com.android.flags");
- assertEquals(flag.flagName, "flag1");
- assertEquals(flag.flagValue, "false");
- assertEquals(flag.isLocal, true);
+ assertEquals(flag.getPackageName(), "com.android.flags");
+ assertEquals(flag.getFlagName(), "flag1");
+ assertEquals("false", flag.getLocalFlagValue());
+ assertEquals("false", flag.getBootFlagValue());
+ assertTrue(flag.getHasLocalOverride());
+ assertNull(flag.getServerFlagValue());
+ assertEquals("false", flag.getDefaultFlagValue());
}
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -1020,18 +1057,25 @@
InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+ Map<String, AconfigdFlagInfo> flags = new HashMap<>();
+ AconfigdFlagInfo flag = AconfigdFlagInfo.newBuilder()
+ .setPackageName("com.android.flags")
+ .setFlagName("flag1")
+ .setBootFlagValue("true").build();
+ flags.put("com.android.flags/flag1", flag);
+
synchronized (lock) {
settingsState.insertSettingLocked("aconfigd_marker/bulk_synced",
"false", null, false, "aconfig");
// first bulk sync
- ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage();
+ ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage(flags);
assertTrue(requests != null);
String value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
assertEquals("true", value);
// send time should no longer bulk sync
- requests = settingsState.handleBulkSyncToNewStorage();
+ requests = settingsState.handleBulkSyncToNewStorage(flags);
assertTrue(requests == null);
value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
assertEquals("true", value);
@@ -1047,21 +1091,200 @@
InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+ Map<String, AconfigdFlagInfo> flags = new HashMap<>();
synchronized (lock) {
settingsState.insertSettingLocked("aconfigd_marker/bulk_synced",
"true", null, false, "aconfig");
// when aconfigd is off, should change the marker to false
- ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage();
+ ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage(flags);
assertTrue(requests == null);
String value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
assertEquals("false", value);
// marker started with false value, after call, it should remain false
- requests = settingsState.handleBulkSyncToNewStorage();
+ requests = settingsState.handleBulkSyncToNewStorage(flags);
assertTrue(requests == null);
value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
assertEquals("false", value);
}
}
+
+ @Test
+ public void testGetAllAconfigFlagsFromSettings() throws Exception {
+ final Object lock = new Object();
+ final PrintStream os = new PrintStream(new FileOutputStream(mSettingsFile));
+ os.print(
+ "<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>"
+ + "<settings version=\"120\">"
+ + " <setting id=\"0\" name=\"test_namespace/com.android.flags.flag1\" "
+ + "value=\"false\" package=\"com.android.flags\" />"
+ + " <setting id=\"1\" name=\"device_config_overrides/test_namespace:com.android.flags.flag1\" "
+ + "value=\"true\" package=\"com.android.flags\" />"
+ + " <setting id=\"2\" name=\"device_config_overrides/test_namespace:com.android.flags.flag2\" "
+ + "value=\"true\" package=\"com.android.flags\" />"
+ + " <setting id=\"3\" name=\"test_namespace/com.android.flags.flag3\" "
+ + "value=\"true\" package=\"com.android.flags\" />"
+ + "</settings>");
+ os.close();
+
+ int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+
+ SettingsState settingsState = new SettingsState(
+ InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+
+ Map<String, AconfigdFlagInfo> ret;
+ synchronized (lock) {
+ ret = settingsState.getAllAconfigFlagsFromSettings();
+ }
+
+ assertTrue(ret.isEmpty());
+
+ parsed_flags flags =
+ parsed_flags
+ .newBuilder()
+ .addParsedFlag(
+ parsed_flag
+ .newBuilder()
+ .setPackage("com.android.flags")
+ .setName("flag1")
+ .setNamespace("test_namespace")
+ .setDescription("test flag")
+ .addBug("12345678")
+ .setState(Aconfig.flag_state.DISABLED)
+ .setPermission(Aconfig.flag_permission.READ_WRITE))
+ .addParsedFlag(
+ parsed_flag
+ .newBuilder()
+ .setPackage("com.android.flags")
+ .setName("flag2")
+ .setNamespace("test_namespace")
+ .setDescription("test flag")
+ .addBug("12345678")
+ .setState(Aconfig.flag_state.DISABLED)
+ .setPermission(Aconfig.flag_permission.READ_WRITE))
+ .addParsedFlag(
+ parsed_flag
+ .newBuilder()
+ .setPackage("com.android.flags")
+ .setName("flag3")
+ .setNamespace("test_namespace")
+ .setDescription("test flag")
+ .addBug("12345678")
+ .setState(Aconfig.flag_state.DISABLED)
+ .setPermission(Aconfig.flag_permission.READ_WRITE))
+ .build();
+
+ Map<String, Map<String, String>> defaults = new HashMap<>();
+ Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>();
+ synchronized (lock) {
+ settingsState.loadAconfigDefaultValues(
+ flags.toByteArray(), defaults, flagInfoDefault);
+ settingsState.addAconfigDefaultValuesFromMap(defaults);
+ ret = settingsState.getAllAconfigFlagsFromSettings();
+ }
+
+ AconfigdFlagInfo expectedFlag1 =
+ AconfigdFlagInfo.newBuilder()
+ .setPackageName("com.android.flags")
+ .setFlagName("flag1")
+ .setServerFlagValue("false")
+ .setLocalFlagValue("true")
+ .setDefaultFlagValue("false")
+ .setBootFlagValue("true")
+ .setHasServerOverride(true)
+ .setHasLocalOverride(true)
+ .setIsReadWrite(false)
+ .build();
+
+ AconfigdFlagInfo expectedFlag2 =
+ AconfigdFlagInfo.newBuilder()
+ .setPackageName("com.android.flags")
+ .setFlagName("flag2")
+ .setLocalFlagValue("true")
+ .setDefaultFlagValue("false")
+ .setBootFlagValue("true")
+ .setHasLocalOverride(true)
+ .setHasServerOverride(false)
+ .setIsReadWrite(false)
+ .build();
+
+
+ AconfigdFlagInfo expectedFlag3 =
+ AconfigdFlagInfo.newBuilder()
+ .setPackageName("com.android.flags")
+ .setFlagName("flag3")
+ .setServerFlagValue("true")
+ .setBootFlagValue("true")
+ .setDefaultFlagValue("false")
+ .setHasServerOverride(true)
+ .setIsReadWrite(false)
+ .build();
+
+ assertEquals(expectedFlag1, ret.get("com.android.flags.flag1"));
+ assertEquals(expectedFlag2, ret.get("com.android.flags.flag2"));
+ assertEquals(expectedFlag3, ret.get("com.android.flags.flag3"));
+ }
+
+ @Test
+ public void testCompareFlagValueInNewStorage() {
+ int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+ Object lock = new Object();
+ SettingsState settingsState =
+ new SettingsState(
+ InstrumentationRegistry.getContext(),
+ lock,
+ mSettingsFile,
+ configKey,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED,
+ Looper.getMainLooper());
+
+ AconfigdFlagInfo defaultFlag1 =
+ AconfigdFlagInfo.newBuilder()
+ .setPackageName("com.android.flags")
+ .setFlagName("flag1")
+ .setDefaultFlagValue("false")
+ .build();
+
+ AconfigdFlagInfo settingFlag1 =
+ AconfigdFlagInfo.newBuilder()
+ .setPackageName("com.android.flags")
+ .setFlagName("flag1")
+ .setServerFlagValue("true")
+ .setHasServerOverride(true)
+ .build();
+
+ AconfigdFlagInfo expectedFlag1 =
+ AconfigdFlagInfo.newBuilder()
+ .setPackageName("com.android.flags")
+ .setFlagName("flag1")
+ .setBootFlagValue("true")
+ .setServerFlagValue("true")
+ .setDefaultFlagValue("false")
+ .setHasServerOverride(true)
+ .build();
+
+ Map<String, AconfigdFlagInfo> settingMap = new HashMap<>();
+ Map<String, AconfigdFlagInfo> aconfigdMap = new HashMap<>();
+ Map<String, AconfigdFlagInfo> defaultMap = new HashMap<>();
+
+ defaultMap.put("com.android.flags.flag1", defaultFlag1);
+ settingMap.put("com.android.flags.flag1", settingFlag1);
+ aconfigdMap.put("com.android.flags.flag1", expectedFlag1);
+
+ int ret = settingsState.compareFlagValueInNewStorage(settingMap, defaultMap, aconfigdMap);
+ assertEquals(0, ret);
+
+ AconfigdFlagInfo defaultFlag2 =
+ AconfigdFlagInfo.newBuilder()
+ .setPackageName("com.android.flags")
+ .setFlagName("flag2")
+ .setDefaultFlagValue("false")
+ .build();
+ defaultMap.put("com.android.flags.flag2", defaultFlag2);
+
+ ret = settingsState.compareFlagValueInNewStorage(settingMap, defaultMap, aconfigdMap);
+ assertEquals(1, ret);
+ }
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 63a52d6..2cb297a 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -33,6 +33,13 @@
}
flag {
+ name: "notification_row_content_binder_refactor"
+ namespace: "systemui"
+ description: "Convert the NotificationContentInflater to Kotlin and restructure it to support modern views"
+ bug: "343942780"
+}
+
+flag {
name: "notification_minimalism_prototype"
namespace: "systemui"
description: "Prototype of notification minimalism; the new 'Intermediate' lockscreen customization proposal."
@@ -407,6 +414,13 @@
}
flag {
+ name: "clock_reactive_variants"
+ namespace: "systemui"
+ description: "Add reactive variant fonts to some clocks"
+ bug: "343495953"
+}
+
+flag {
name: "fast_unlock_transition"
namespace: "systemui"
description: "Faster wallpaper unlock transition"
@@ -736,16 +750,6 @@
}
flag {
- name: "trim_resources_with_background_trim_at_lock"
- namespace: "systemui"
- description: "Trim fonts and other caches when the device locks to lower memory consumption."
- bug: "322143614"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "dedicated_notif_inflation_thread"
namespace: "systemui"
description: "Create a separate background thread for inflating notifications"
@@ -1011,6 +1015,13 @@
}
flag {
+ name: "glanceable_hub_allow_keyguard_when_dreaming"
+ namespace: "systemui"
+ description: "Allows users to exit dream to keyguard with glanceable hub enabled"
+ bug: "343505271"
+}
+
+flag {
name: "new_touchpad_gestures_tutorial"
namespace: "systemui"
description: "Enables new interactive tutorial for learning touchpad gestures"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxConfig.kt
new file mode 100644
index 0000000..72f0e86
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxConfig.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.surfaceeffects.glowboxeffect
+
+/** Parameters used to play [GlowBoxEffect]. */
+data class GlowBoxConfig(
+ /** Start center position X in px. */
+ val startCenterX: Float,
+ /** Start center position Y in px. */
+ val startCenterY: Float,
+ /** End center position X in px. */
+ val endCenterX: Float,
+ /** End center position Y in px. */
+ val endCenterY: Float,
+ /** Width of the box in px. */
+ val width: Float,
+ /** Height of the box in px. */
+ val height: Float,
+ /** Color of the box in ARGB, Apply alpha value if needed. */
+ val color: Int,
+ /** Amount of blur (or glow) of the box. */
+ val blurAmount: Float,
+ /**
+ * Duration of the animation. Note that the full duration of the animation is
+ * [duration] + [easeInDuration] + [easeOutDuration].
+ */
+ val duration: Long,
+ /** Ease in duration of the animation. */
+ val easeInDuration: Long,
+ /** Ease out duration of the animation. */
+ val easeOutDuration: Long,
+)
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffect.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffect.kt
new file mode 100644
index 0000000..5e590c1
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffect.kt
@@ -0,0 +1,185 @@
+/*
+ * 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.surfaceeffects.glowboxeffect
+
+import android.animation.ValueAnimator
+import android.graphics.Paint
+import androidx.annotation.VisibleForTesting
+import androidx.core.animation.doOnEnd
+import com.android.systemui.surfaceeffects.PaintDrawCallback
+import com.android.systemui.surfaceeffects.utils.MathUtils.lerp
+
+/** Glow box effect where the box moves from start to end positions defined in the [config]. */
+class GlowBoxEffect(
+ private var config: GlowBoxConfig,
+ private val paintDrawCallback: PaintDrawCallback,
+ private val stateChangedCallback: AnimationStateChangedCallback? = null
+) {
+ private val glowBoxShader =
+ GlowBoxShader().apply {
+ setSize(config.width, config.height)
+ setCenter(config.startCenterX, config.startCenterY)
+ setBlur(config.blurAmount)
+ setColor(config.color)
+ }
+ private var animator: ValueAnimator? = null
+ @VisibleForTesting var state: AnimationState = AnimationState.NOT_PLAYING
+ private val paint = Paint().apply { shader = glowBoxShader }
+
+ fun updateConfig(newConfig: GlowBoxConfig) {
+ this.config = newConfig
+
+ with(glowBoxShader) {
+ setSize(config.width, config.height)
+ setCenter(config.startCenterX, config.startCenterY)
+ setBlur(config.blurAmount)
+ setColor(config.color)
+ }
+ }
+
+ fun play() {
+ if (state != AnimationState.NOT_PLAYING) {
+ return
+ }
+
+ playEaseIn()
+ }
+
+ /** Finishes the animation with ease out. */
+ fun finish(force: Boolean = false) {
+ // If it's playing ease out, cancel immediately.
+ if (force && state == AnimationState.EASE_OUT) {
+ animator?.cancel()
+ return
+ }
+
+ // If it's playing either ease in or main, fast-forward to ease out.
+ if (state == AnimationState.EASE_IN || state == AnimationState.MAIN) {
+ animator?.pause()
+ playEaseOut()
+ }
+
+ // At this point, animation state should be ease out. Cancel it if force is true.
+ if (force) {
+ animator?.cancel()
+ }
+ }
+
+ private fun playEaseIn() {
+ if (state == AnimationState.EASE_IN) {
+ return
+ }
+ state = AnimationState.EASE_IN
+ stateChangedCallback?.onStart()
+
+ animator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = config.easeInDuration
+ addUpdateListener {
+ val progress = it.animatedValue as Float
+ glowBoxShader.setCenter(
+ lerp(config.startCenterX, config.endCenterX, progress),
+ lerp(config.startCenterY, config.endCenterY, progress)
+ )
+
+ draw()
+ }
+
+ doOnEnd {
+ animator = null
+ playMain()
+ }
+
+ start()
+ }
+ }
+
+ private fun playMain() {
+ if (state == AnimationState.MAIN) {
+ return
+ }
+ state = AnimationState.MAIN
+
+ animator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = config.duration
+ addUpdateListener { draw() }
+
+ doOnEnd {
+ animator = null
+ playEaseOut()
+ }
+
+ start()
+ }
+ }
+
+ private fun playEaseOut() {
+ if (state == AnimationState.EASE_OUT) return
+ state = AnimationState.EASE_OUT
+
+ animator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = config.easeOutDuration
+ addUpdateListener {
+ val progress = it.animatedValue as Float
+ glowBoxShader.setCenter(
+ lerp(config.endCenterX, config.startCenterX, progress),
+ lerp(config.endCenterY, config.startCenterY, progress)
+ )
+
+ draw()
+ }
+
+ doOnEnd {
+ animator = null
+ state = AnimationState.NOT_PLAYING
+ stateChangedCallback?.onEnd()
+ }
+
+ start()
+ }
+ }
+
+ private fun draw() {
+ paintDrawCallback.onDraw(paint)
+ }
+
+ /**
+ * The animation state of the effect. The animation state transitions as follows: [EASE_IN] ->
+ * [MAIN] -> [EASE_OUT] -> [NOT_PLAYING].
+ */
+ enum class AnimationState {
+ EASE_IN,
+ MAIN,
+ EASE_OUT,
+ NOT_PLAYING,
+ }
+
+ interface AnimationStateChangedCallback {
+ /**
+ * Triggered when the animation starts, specifically when the states goes from
+ * [AnimationState.NOT_PLAYING] to [AnimationState.EASE_IN].
+ */
+ fun onStart()
+ /**
+ * Triggered when the animation ends, specifically when the states goes from
+ * [AnimationState.EASE_OUT] to [AnimationState.NOT_PLAYING].
+ */
+ fun onEnd()
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxShader.kt
new file mode 100644
index 0000000..3693408
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxShader.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.surfaceeffects.glowboxeffect
+
+import android.graphics.RuntimeShader
+import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary
+
+/** Soft box shader. */
+class GlowBoxShader : RuntimeShader(GLOW_SHADER) {
+ // language=AGSL
+ private companion object {
+ private const val SHADER =
+ """
+ uniform half2 in_center;
+ uniform half2 in_size;
+ uniform half in_blur;
+ layout(color) uniform half4 in_color;
+
+ float4 main(float2 fragcoord) {
+ half glow = soften(sdBox(fragcoord - in_center, in_size), in_blur);
+ return in_color * (1. - glow);
+ }
+ """
+
+ private const val GLOW_SHADER =
+ SdfShaderLibrary.BOX_SDF + SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SHADER
+ }
+
+ fun setCenter(x: Float, y: Float) {
+ setFloatUniform("in_center", x, y)
+ }
+
+ fun setSize(width: Float, height: Float) {
+ setFloatUniform("in_size", width, height)
+ }
+
+ fun setBlur(blurAmount: Float) {
+ setFloatUniform("in_blur", blurAmount)
+ }
+
+ fun setColor(color: Int) {
+ setColorUniform("in_color", color)
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
index 7889893..4efab58 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
@@ -35,17 +35,26 @@
}
"""
+ const val BOX_SDF =
+ """
+ float sdBox(vec2 p, vec2 size) {
+ size = size * 0.5;
+ vec2 d = abs(p) - size;
+ return length(max(d, 0.)) + min(max(d.x, d.y), 0.) / size.y;
+ }
+ """
+
const val ROUNDED_BOX_SDF =
"""
float sdRoundedBox(vec2 p, vec2 size, float cornerRadius) {
size *= 0.5;
cornerRadius *= 0.5;
- vec2 d = abs(p)-size+cornerRadius;
+ vec2 d = abs(p) - size + cornerRadius;
float outside = length(max(d, 0.0));
float inside = min(max(d.x, d.y), 0.0);
- return (outside+inside-cornerRadius)/size.y;
+ return (outside + inside - cornerRadius) / size.y;
}
float roundedBoxRing(vec2 p, vec2 size, float cornerRadius,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt
new file mode 100644
index 0000000..7ed3b87
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.surfaceeffects.utils
+
+/** Copied from android.utils.MathUtils */
+object MathUtils {
+ fun lerp(start: Float, stop: Float, amount: Float): Float {
+ return start + (stop - start) * amount
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index a90f82e..c329384 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -1,9 +1,16 @@
package com.android.systemui.communal.ui.compose
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
+import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@@ -13,12 +20,20 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.CommunalSwipeDetector
@@ -36,8 +51,10 @@
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
+import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.Flags
import com.android.systemui.Flags.glanceableHubFullscreenSwipe
+import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.communal.ui.compose.extensions.allowGestures
@@ -102,6 +119,10 @@
val touchesAllowed by viewModel.touchesAllowed.collectAsStateWithLifecycle(initialValue = false)
val showGestureIndicator by
viewModel.showGestureIndicator.collectAsStateWithLifecycle(initialValue = false)
+ val backgroundType by
+ viewModel.communalBackground.collectAsStateWithLifecycle(
+ initialValue = CommunalBackgroundType.DEFAULT
+ )
val state: MutableSceneTransitionLayoutState = remember {
MutableSceneTransitionLayoutState(
initialScene = currentSceneKey,
@@ -174,7 +195,7 @@
userActions =
mapOf(Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank)
) {
- CommunalScene(colors, content)
+ CommunalScene(backgroundType, colors, content)
}
}
@@ -186,17 +207,87 @@
/** Scene containing the glanceable hub UI. */
@Composable
private fun SceneScope.CommunalScene(
+ backgroundType: CommunalBackgroundType,
colors: CommunalColors,
content: CommunalContent,
modifier: Modifier = Modifier,
) {
- val backgroundColor by colors.backgroundColor.collectAsStateWithLifecycle()
-
- Box(
- modifier =
- Modifier.element(Communal.Elements.Scrim)
- .fillMaxSize()
- .background(Color(backgroundColor.toArgb())),
- )
+ Box(modifier = Modifier.element(Communal.Elements.Scrim).fillMaxSize()) {
+ when (backgroundType) {
+ CommunalBackgroundType.DEFAULT -> DefaultBackground(colors = colors)
+ CommunalBackgroundType.STATIC_GRADIENT -> StaticLinearGradient()
+ CommunalBackgroundType.ANIMATED -> AnimatedLinearGradient()
+ }
+ }
with(content) { Content(modifier = modifier) }
}
+
+/** Default background of the hub, a single color */
+@Composable
+private fun BoxScope.DefaultBackground(
+ colors: CommunalColors,
+) {
+ val backgroundColor by colors.backgroundColor.collectAsStateWithLifecycle()
+ Box(
+ modifier = Modifier.matchParentSize().background(Color(backgroundColor.toArgb())),
+ )
+}
+
+/** Experimental hub background, static linear gradient */
+@Composable
+private fun BoxScope.StaticLinearGradient() {
+ val colors = LocalAndroidColorScheme.current
+ Box(
+ Modifier.matchParentSize()
+ .background(
+ Brush.linearGradient(colors = listOf(colors.primary, colors.primaryContainer)),
+ )
+ )
+ BackgroundTopScrim()
+}
+
+/** Experimental hub background, animated linear gradient */
+@Composable
+private fun BoxScope.AnimatedLinearGradient() {
+ val colors = LocalAndroidColorScheme.current
+ Box(
+ Modifier.matchParentSize()
+ .animatedGradientBackground(colors = listOf(colors.primary, colors.primaryContainer))
+ )
+ BackgroundTopScrim()
+}
+
+/** Scrim placed on top of the background in order to dim/bright colors */
+@Composable
+private fun BoxScope.BackgroundTopScrim() {
+ val darkTheme = isSystemInDarkTheme()
+ val scrimOnTopColor = if (darkTheme) Color.Black else Color.White
+ Box(Modifier.matchParentSize().alpha(0.34f).background(scrimOnTopColor))
+}
+
+/** Modifier which sets the background of a composable to an animated gradient */
+@Composable
+private fun Modifier.animatedGradientBackground(colors: List<Color>): Modifier = composed {
+ var size by remember { mutableStateOf(IntSize.Zero) }
+ val transition = rememberInfiniteTransition(label = "scrim background")
+ val startOffsetX by
+ transition.animateFloat(
+ initialValue = -size.width.toFloat(),
+ targetValue = size.width.toFloat(),
+ animationSpec =
+ infiniteRepeatable(
+ animation = tween(durationMillis = 5_000, easing = LinearEasing),
+ repeatMode = RepeatMode.Reverse,
+ ),
+ label = "scrim start offset"
+ )
+ background(
+ brush =
+ Brush.linearGradient(
+ colors = colors,
+ start = Offset(startOffsetX, 0f),
+ end = Offset(startOffsetX + size.width.toFloat(), size.height.toFloat()),
+ )
+ )
+ .onGloballyPositioned { size = it.size }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index ee3ffce..27a834b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -170,6 +170,7 @@
maxScrimTop: () -> Float,
shouldPunchHoleBehindScrim: Boolean,
shouldFillMaxSize: Boolean = true,
+ shouldReserveSpaceForNavBar: Boolean = true,
shadeMode: ShadeMode,
modifier: Modifier = Modifier,
) {
@@ -353,7 +354,7 @@
.fillMaxWidth()
.notificationStackHeight(
view = stackScrollView,
- padding = navBarHeight.toInt()
+ padding = if (shouldReserveSpaceForNavBar) navBarHeight.toInt() else 0
)
.onSizeChanged { size -> stackHeight.intValue = size.height },
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
index 7ca68fb..edb1727 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
@@ -94,6 +94,7 @@
maxScrimTop = { 0f },
shouldPunchHoleBehindScrim = false,
shouldFillMaxSize = false,
+ shouldReserveSpaceForNavBar = false,
shadeMode = ShadeMode.Dual,
modifier = Modifier.fillMaxWidth(),
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
index 4d946bf..4bf90ec 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
@@ -16,22 +16,44 @@
package com.android.systemui.qs.ui.composable
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.composable.LockscreenContent
+import com.android.systemui.qs.panels.ui.compose.EditMode
+import com.android.systemui.qs.panels.ui.compose.TileGrid
import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneViewModel
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
import com.android.systemui.shade.ui.composable.OverlayShade
-import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
+import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.phone.ui.TintedIconManager
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
@@ -41,9 +63,12 @@
class QuickSettingsShadeScene
@Inject
constructor(
- viewModel: QuickSettingsShadeSceneViewModel,
- private val overlayShadeViewModel: OverlayShadeViewModel,
+ private val viewModel: QuickSettingsShadeSceneViewModel,
private val lockscreenContent: Lazy<Optional<LockscreenContent>>,
+ private val shadeHeaderViewModel: ShadeHeaderViewModel,
+ private val tintedIconManagerFactory: TintedIconManager.Factory,
+ private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
+ private val statusBarIconController: StatusBarIconController,
) : ComposableScene {
override val key = Scenes.QuickSettingsShade
@@ -56,21 +81,101 @@
modifier: Modifier,
) {
OverlayShade(
- viewModel = overlayShadeViewModel,
- modifier = modifier,
+ viewModel = viewModel.overlayShadeViewModel,
horizontalArrangement = Arrangement.End,
lockscreenContent = lockscreenContent,
+ modifier = modifier,
) {
- Text(
- text = "Quick settings grid",
- modifier = Modifier.padding(QuickSettingsShade.Dimensions.Padding)
+ Column {
+ ExpandedShadeHeader(
+ viewModel = shadeHeaderViewModel,
+ createTintedIconManager = tintedIconManagerFactory::create,
+ createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
+ statusBarIconController = statusBarIconController,
+ modifier = Modifier.padding(QuickSettingsShade.Dimensions.Padding),
+ )
+
+ ShadeBody(
+ viewModel = viewModel,
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun ShadeBody(
+ viewModel: QuickSettingsShadeSceneViewModel,
+) {
+ val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle()
+
+ Box {
+ // The main Quick Settings grid layout.
+ AnimatedVisibility(
+ visible = !isEditing,
+ enter = QuickSettingsShade.Transitions.QuickSettingsLayoutEnter,
+ exit = QuickSettingsShade.Transitions.QuickSettingsLayoutExit,
+ ) {
+ QuickSettingsLayout(
+ viewModel = viewModel,
+ )
+ }
+
+ // The Quick Settings Editor layout.
+ AnimatedVisibility(
+ visible = isEditing,
+ enter = QuickSettingsShade.Transitions.QuickSettingsEditorEnter,
+ exit = QuickSettingsShade.Transitions.QuickSettingsEditorExit,
+ ) {
+ EditMode(
+ viewModel = viewModel.editModeViewModel,
+ modifier = Modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding)
)
}
}
}
+@Composable
+private fun QuickSettingsLayout(
+ viewModel: QuickSettingsShadeSceneViewModel,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(QuickSettingsShade.Dimensions.Padding),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding),
+ ) {
+ BrightnessSliderContainer(
+ viewModel = viewModel.brightnessSliderViewModel,
+ modifier =
+ Modifier.fillMaxWidth()
+ .height(QuickSettingsShade.Dimensions.BrightnessSliderHeight),
+ )
+ TileGrid(
+ viewModel = viewModel.tileGridViewModel,
+ modifier =
+ Modifier.fillMaxWidth().heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight),
+ )
+ Button(
+ onClick = { viewModel.editModeViewModel.startEditing() },
+ ) {
+ Text("Edit mode")
+ }
+ }
+}
+
object QuickSettingsShade {
+
object Dimensions {
val Padding = 16.dp
+ val BrightnessSliderHeight = 64.dp
+ val GridMaxHeight = 400.dp
+ }
+
+ object Transitions {
+ val QuickSettingsLayoutEnter: EnterTransition = fadeIn(tween(500))
+ val QuickSettingsLayoutExit: ExitTransition = fadeOut(tween(500))
+ val QuickSettingsEditorEnter: EnterTransition = fadeIn(tween(500))
+ val QuickSettingsEditorExit: ExitTransition = fadeOut(tween(500))
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index 3255b08..10c4030 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -104,9 +104,9 @@
)
}
overscroll(Scenes.NotificationsShade, Orientation.Vertical) {
- translate(OverlayShade.Elements.Panel, y = { OverlayShade.Dimensions.OverscrollLimit })
+ translate(OverlayShade.Elements.Panel, y = OverlayShade.Dimensions.OverscrollLimit)
}
overscroll(Scenes.QuickSettingsShade, Orientation.Vertical) {
- translate(OverlayShade.Elements.Panel, y = { OverlayShade.Dimensions.OverscrollLimit })
+ translate(OverlayShade.Elements.Panel, y = OverlayShade.Dimensions.OverscrollLimit)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index a730206..6924d43 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -14,17 +14,28 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalLayoutApi::class)
+
package com.android.systemui.shade.ui.composable
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.calculateEndPadding
+import androidx.compose.foundation.layout.calculateStartPadding
+import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBarsIgnoringVisibility
+import androidx.compose.foundation.layout.waterfall
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
@@ -35,12 +46,12 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexScenePicker
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.modifiers.thenIf
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.keyguard.ui.composable.LockscreenContent
import com.android.systemui.scene.shared.model.Scenes
@@ -59,8 +70,6 @@
content: @Composable () -> Unit,
) {
val backgroundScene by viewModel.backgroundScene.collectAsStateWithLifecycle()
- val widthSizeClass = LocalWindowSizeClass.current.widthSizeClass
- val isPanelFullWidth = widthSizeClass == WindowWidthSizeClass.Compact
Box(modifier) {
if (backgroundScene == Scenes.Lockscreen) {
@@ -73,10 +82,7 @@
Scrim(onClicked = viewModel::onScrimClicked)
Row(
- modifier =
- Modifier.fillMaxSize().thenIf(!isPanelFullWidth) {
- Modifier.padding(OverlayShade.Dimensions.ScrimContentPadding)
- },
+ modifier = Modifier.fillMaxSize().panelPadding(),
horizontalArrangement = horizontalArrangement,
) {
Panel(
@@ -138,6 +144,42 @@
)
}
+@Composable
+private fun Modifier.panelPadding(): Modifier {
+ val widthSizeClass = LocalWindowSizeClass.current.widthSizeClass
+ val systemBars = WindowInsets.systemBarsIgnoringVisibility
+ val displayCutout = WindowInsets.displayCutout
+ val waterfall = WindowInsets.waterfall
+ val contentPadding = PaddingValues(all = OverlayShade.Dimensions.ScrimContentPadding)
+
+ val combinedPadding =
+ combinePaddings(
+ systemBars.asPaddingValues(),
+ displayCutout.asPaddingValues(),
+ waterfall.asPaddingValues(),
+ contentPadding
+ )
+
+ return if (widthSizeClass == WindowWidthSizeClass.Compact) {
+ padding(bottom = combinedPadding.calculateBottomPadding())
+ } else {
+ padding(combinedPadding)
+ }
+}
+
+/** Creates a union of [paddingValues] by using the max padding of each edge. */
+@Composable
+private fun combinePaddings(vararg paddingValues: PaddingValues): PaddingValues {
+ val layoutDirection = LocalLayoutDirection.current
+
+ return PaddingValues(
+ start = paddingValues.maxOfOrNull { it.calculateStartPadding(layoutDirection) } ?: 0.dp,
+ top = paddingValues.maxOfOrNull { it.calculateTopPadding() } ?: 0.dp,
+ end = paddingValues.maxOfOrNull { it.calculateEndPadding(layoutDirection) } ?: 0.dp,
+ bottom = paddingValues.maxOfOrNull { it.calculateBottomPadding() } ?: 0.dp
+ )
+}
+
object OverlayShade {
object Elements {
val Scrim = ElementKey("OverlayShadeScrim", scenePicker = LowestZIndexScenePicker)
@@ -157,7 +199,7 @@
val PanelCornerRadius = 46.dp
val PanelWidthMedium = 390.dp
val PanelWidthLarge = 474.dp
- val OverscrollLimit = 100f
+ val OverscrollLimit = 32.dp
}
object Shapes {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
index da29d58..48af8cd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
@@ -16,16 +16,13 @@
package com.android.systemui.volume.panel.component.spatialaudio
-import com.android.systemui.volume.panel.component.button.ui.composable.ButtonComponent
import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
import com.android.systemui.volume.panel.component.spatial.domain.SpatialAudioAvailabilityCriteria
-import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.SpatialAudioViewModel
-import com.android.systemui.volume.panel.component.spatialaudio.ui.composable.SpatialAudioPopup
+import com.android.systemui.volume.panel.component.spatialaudio.ui.composable.SpatialAudioComponent
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
import dagger.Binds
import dagger.Module
-import dagger.Provides
import dagger.multibindings.IntoMap
import dagger.multibindings.StringKey
@@ -40,14 +37,8 @@
criteria: SpatialAudioAvailabilityCriteria
): ComponentAvailabilityCriteria
- companion object {
-
- @Provides
- @IntoMap
- @StringKey(VolumePanelComponents.SPATIAL_AUDIO)
- fun provideVolumePanelUiComponent(
- viewModel: SpatialAudioViewModel,
- popup: SpatialAudioPopup,
- ): VolumePanelUiComponent = ButtonComponent(viewModel.spatialAudioButton, popup::show)
- }
+ @Binds
+ @IntoMap
+ @StringKey(VolumePanelComponents.SPATIAL_AUDIO)
+ fun bindVolumePanelUiComponent(component: SpatialAudioComponent): VolumePanelUiComponent
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioComponent.kt
new file mode 100644
index 0000000..2d89b5c
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioComponent.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.panel.component.spatialaudio.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.volume.panel.component.button.ui.composable.ButtonComponent
+import com.android.systemui.volume.panel.component.button.ui.composable.ToggleButtonComponent
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
+import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.SpatialAudioViewModel
+import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
+import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
+import javax.inject.Inject
+
+/** [ComposeVolumePanelUiComponent] that represents spatial audio button in the Volume Panel. */
+class SpatialAudioComponent
+@Inject
+constructor(
+ private val viewModel: SpatialAudioViewModel,
+ private val popup: SpatialAudioPopup,
+) : ComposeVolumePanelUiComponent {
+
+ @Composable
+ override fun VolumePanelComposeScope.Content(modifier: Modifier) {
+ val shouldUsePopup by viewModel.shouldUsePopup.collectAsStateWithLifecycle()
+
+ val buttonComponent: ComposeVolumePanelUiComponent =
+ remember(shouldUsePopup) {
+ if (shouldUsePopup) {
+ ButtonComponent(viewModel.spatialAudioButton, popup::show)
+ } else {
+ ToggleButtonComponent(viewModel.spatialAudioButton) {
+ if (it) {
+ viewModel.setEnabled(SpatialAudioEnabledModel.SpatialAudioEnabled)
+ } else {
+ viewModel.setEnabled(SpatialAudioEnabledModel.Disabled)
+ }
+ }
+ }
+ }
+ with(buttonComponent) { Content(modifier) }
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 5d1a7c5..7fd3a176 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -27,11 +27,12 @@
import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.lerp
+import androidx.compose.ui.graphics.colorspace.ColorSpaces
import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.lerp
+import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastCoerceIn
-import androidx.compose.ui.util.lerp
+import androidx.compose.ui.util.fastLastOrNull
+import kotlin.math.roundToInt
/**
* A [State] whose [value] is animated.
@@ -74,7 +75,7 @@
key: ValueKey,
canOverflow: Boolean = true,
): AnimatedState<Int> {
- return animateSceneValueAsState(value, key, ::lerp, canOverflow)
+ return animateSceneValueAsState(value, key, SharedIntType, canOverflow)
}
/**
@@ -88,7 +89,19 @@
key: ValueKey,
canOverflow: Boolean = true,
): AnimatedState<Int> {
- return animateElementValueAsState(value, key, ::lerp, canOverflow)
+ return animateElementValueAsState(value, key, SharedIntType, canOverflow)
+}
+
+private object SharedIntType : SharedValueType<Int, Int> {
+ override val unspecifiedValue: Int = Int.MIN_VALUE
+ override val zeroDeltaValue: Int = 0
+
+ override fun lerp(a: Int, b: Int, progress: Float): Int =
+ androidx.compose.ui.util.lerp(a, b, progress)
+
+ override fun diff(a: Int, b: Int): Int = a - b
+
+ override fun addWeighted(a: Int, b: Int, bWeight: Float): Int = (a + b * bWeight).roundToInt()
}
/**
@@ -102,7 +115,7 @@
key: ValueKey,
canOverflow: Boolean = true,
): AnimatedState<Float> {
- return animateSceneValueAsState(value, key, ::lerp, canOverflow)
+ return animateSceneValueAsState(value, key, SharedFloatType, canOverflow)
}
/**
@@ -116,7 +129,19 @@
key: ValueKey,
canOverflow: Boolean = true,
): AnimatedState<Float> {
- return animateElementValueAsState(value, key, ::lerp, canOverflow)
+ return animateElementValueAsState(value, key, SharedFloatType, canOverflow)
+}
+
+private object SharedFloatType : SharedValueType<Float, Float> {
+ override val unspecifiedValue: Float = Float.MIN_VALUE
+ override val zeroDeltaValue: Float = 0f
+
+ override fun lerp(a: Float, b: Float, progress: Float): Float =
+ androidx.compose.ui.util.lerp(a, b, progress)
+
+ override fun diff(a: Float, b: Float): Float = a - b
+
+ override fun addWeighted(a: Float, b: Float, bWeight: Float): Float = a + b * bWeight
}
/**
@@ -130,7 +155,7 @@
key: ValueKey,
canOverflow: Boolean = true,
): AnimatedState<Dp> {
- return animateSceneValueAsState(value, key, ::lerp, canOverflow)
+ return animateSceneValueAsState(value, key, SharedDpType, canOverflow)
}
/**
@@ -144,7 +169,20 @@
key: ValueKey,
canOverflow: Boolean = true,
): AnimatedState<Dp> {
- return animateElementValueAsState(value, key, ::lerp, canOverflow)
+ return animateElementValueAsState(value, key, SharedDpType, canOverflow)
+}
+
+private object SharedDpType : SharedValueType<Dp, Dp> {
+ override val unspecifiedValue: Dp = Dp.Unspecified
+ override val zeroDeltaValue: Dp = 0.dp
+
+ override fun lerp(a: Dp, b: Dp, progress: Float): Dp {
+ return androidx.compose.ui.unit.lerp(a, b, progress)
+ }
+
+ override fun diff(a: Dp, b: Dp): Dp = a - b
+
+ override fun addWeighted(a: Dp, b: Dp, bWeight: Float): Dp = a + b * bWeight
}
/**
@@ -157,7 +195,7 @@
value: Color,
key: ValueKey,
): AnimatedState<Color> {
- return animateSceneValueAsState(value, key, ::lerp, canOverflow = false)
+ return animateSceneValueAsState(value, key, SharedColorType, canOverflow = false)
}
/**
@@ -170,9 +208,56 @@
value: Color,
key: ValueKey,
): AnimatedState<Color> {
- return animateElementValueAsState(value, key, ::lerp, canOverflow = false)
+ return animateElementValueAsState(value, key, SharedColorType, canOverflow = false)
}
+private object SharedColorType : SharedValueType<Color, ColorDelta> {
+ override val unspecifiedValue: Color = Color.Unspecified
+ override val zeroDeltaValue: ColorDelta = ColorDelta(0f, 0f, 0f, 0f)
+
+ override fun lerp(a: Color, b: Color, progress: Float): Color {
+ return androidx.compose.ui.graphics.lerp(a, b, progress)
+ }
+
+ override fun diff(a: Color, b: Color): ColorDelta {
+ // Similar to lerp, we convert colors to the Oklab color space to perform operations on
+ // colors.
+ val aOklab = a.convert(ColorSpaces.Oklab)
+ val bOklab = b.convert(ColorSpaces.Oklab)
+ return ColorDelta(
+ red = aOklab.red - bOklab.red,
+ green = aOklab.green - bOklab.green,
+ blue = aOklab.blue - bOklab.blue,
+ alpha = aOklab.alpha - bOklab.alpha,
+ )
+ }
+
+ override fun addWeighted(a: Color, b: ColorDelta, bWeight: Float): Color {
+ val aOklab = a.convert(ColorSpaces.Oklab)
+ return Color(
+ red = aOklab.red + b.red * bWeight,
+ green = aOklab.green + b.green * bWeight,
+ blue = aOklab.blue + b.blue * bWeight,
+ alpha = aOklab.alpha + b.alpha * bWeight,
+ colorSpace = ColorSpaces.Oklab,
+ )
+ .convert(aOklab.colorSpace)
+ }
+}
+
+/**
+ * Represents the diff between two colors in the same color space.
+ *
+ * Note: This class is necessary because Color() checks the bounds of its values and UncheckedColor
+ * is internal.
+ */
+private class ColorDelta(
+ val red: Float,
+ val green: Float,
+ val blue: Float,
+ val alpha: Float,
+)
+
@Composable
internal fun <T> animateSharedValueAsState(
layoutImpl: SceneTransitionLayoutImpl,
@@ -180,23 +265,22 @@
element: ElementKey?,
key: ValueKey,
value: T,
- lerp: (T, T, Float) -> T,
+ type: SharedValueType<T, *>,
canOverflow: Boolean,
): AnimatedState<T> {
DisposableEffect(layoutImpl, scene, element, key) {
// Create the associated maps that hold the current value for each (element, scene) pair.
val valueMap = layoutImpl.sharedValues.getOrPut(key) { mutableMapOf() }
- val sceneToValueMap =
- valueMap.getOrPut(element) { SnapshotStateMap<SceneKey, Any>() }
- as SnapshotStateMap<SceneKey, T>
- sceneToValueMap[scene] = value
+ val sharedValue = valueMap.getOrPut(element) { SharedValue(type) } as SharedValue<T, *>
+ val targetValues = sharedValue.targetValues
+ targetValues[scene] = value
onDispose {
// Remove the value associated to the current scene, and eventually remove the maps if
// they are empty.
- sceneToValueMap.remove(scene)
+ targetValues.remove(scene)
- if (sceneToValueMap.isEmpty() && valueMap[element] === sceneToValueMap) {
+ if (targetValues.isEmpty() && valueMap[element] === sharedValue) {
valueMap.remove(element)
if (valueMap.isEmpty() && layoutImpl.sharedValues[key] === valueMap) {
@@ -208,34 +292,25 @@
// Update the current value. Note that side effects run after disposable effects, so we know
// that the associated maps were created at this point.
- SideEffect { sceneToValueMap<T>(layoutImpl, key, element)[scene] = value }
-
- return remember(layoutImpl, scene, element, lerp, canOverflow) {
- object : AnimatedState<T> {
- override val value: T
- get() = value(layoutImpl, scene, element, key, lerp, canOverflow)
-
- @Composable
- override fun unsafeCompositionState(initialValue: T): State<T> {
- val state = remember { mutableStateOf(initialValue) }
-
- val animatedState = this
- LaunchedEffect(animatedState) {
- snapshotFlow { animatedState.value }.collect { state.value = it }
- }
-
- return state
- }
+ SideEffect {
+ if (value == type.unspecifiedValue) {
+ error("value is equal to $value, which is the undefined value for this type.")
}
+
+ sharedValue<T, Any>(layoutImpl, key, element).targetValues[scene] = value
+ }
+
+ return remember(layoutImpl, scene, element, canOverflow) {
+ AnimatedStateImpl<T, Any>(layoutImpl, scene, element, key, canOverflow)
}
}
-private fun <T> sceneToValueMap(
+private fun <T, Delta> sharedValue(
layoutImpl: SceneTransitionLayoutImpl,
key: ValueKey,
element: ElementKey?
-): MutableMap<SceneKey, T> {
- return layoutImpl.sharedValues[key]?.get(element)?.let { it as SnapshotStateMap<SceneKey, T> }
+): SharedValue<T, Delta> {
+ return layoutImpl.sharedValues[key]?.get(element)?.let { it as SharedValue<T, Delta> }
?: error(valueReadTooEarlyMessage(key))
}
@@ -244,62 +319,155 @@
"means that you are reading it during composition, which you should not do. See the " +
"documentation of AnimatedState for more information."
-private fun <T> value(
- layoutImpl: SceneTransitionLayoutImpl,
- scene: SceneKey,
- element: ElementKey?,
- key: ValueKey,
- lerp: (T, T, Float) -> T,
- canOverflow: Boolean,
-): T {
- return valueOrNull(layoutImpl, scene, element, key, lerp, canOverflow)
- ?: error(valueReadTooEarlyMessage(key))
+internal class SharedValue<T, Delta>(
+ val type: SharedValueType<T, Delta>,
+) {
+ /** The target value of this shared value for each scene. */
+ val targetValues = SnapshotStateMap<SceneKey, T>()
+
+ /** The last value of this shared value. */
+ var lastValue: T = type.unspecifiedValue
+
+ /** The value of this shared value before the last interruption (if any). */
+ var valueBeforeInterruption: T = type.unspecifiedValue
+
+ /** The delta value to add to this shared value to have smoother interruptions. */
+ var valueInterruptionDelta = type.zeroDeltaValue
+
+ /** The last transition that was used when the value of this shared state. */
+ var lastTransition: TransitionState.Transition? = null
}
-private fun <T> valueOrNull(
- layoutImpl: SceneTransitionLayoutImpl,
- scene: SceneKey,
- element: ElementKey?,
- key: ValueKey,
- lerp: (T, T, Float) -> T,
- canOverflow: Boolean,
-): T? {
- val sceneToValueMap = sceneToValueMap<T>(layoutImpl, key, element)
- fun sceneValue(scene: SceneKey): T? = sceneToValueMap[scene]
+private class AnimatedStateImpl<T, Delta>(
+ private val layoutImpl: SceneTransitionLayoutImpl,
+ private val scene: SceneKey,
+ private val element: ElementKey?,
+ private val key: ValueKey,
+ private val canOverflow: Boolean,
+) : AnimatedState<T> {
+ override val value: T
+ get() = value()
- return when (val transition = layoutImpl.state.transitionState) {
- is TransitionState.Idle -> sceneValue(transition.currentScene)
- is TransitionState.Transition -> {
- // Note: no need to check for transition ready here given that all target values are
- // defined during composition, we should already have the correct values to interpolate
- // between here.
- val fromValue = sceneValue(transition.fromScene)
- val toValue = sceneValue(transition.toScene)
- if (fromValue != null && toValue != null) {
- if (fromValue == toValue) {
- // Optimization: avoid reading progress if the values are the same, so we don't
- // relayout/redraw for nothing.
- fromValue
- } else {
- // In the case of bouncing, if the value remains constant during the overscroll,
- // we should use the value of the scene we are bouncing around.
- if (!canOverflow && transition is TransitionState.HasOverscrollProperties) {
- val bouncingScene = transition.bouncingScene
- if (bouncingScene != null) {
- return sceneValue(bouncingScene)
- }
+ private fun value(): T {
+ val sharedValue = sharedValue<T, Delta>(layoutImpl, key, element)
+ val transition = transition(sharedValue)
+ val value: T =
+ valueOrNull(sharedValue, transition)
+ // TODO(b/311600838): Remove this. We should not have to fallback to the current
+ // scene value, but we have to because code of removed nodes can still run if they
+ // are placed with a graphics layer.
+ ?: sharedValue[scene]
+ ?: error(valueReadTooEarlyMessage(key))
+ val interruptedValue = computeInterruptedValue(sharedValue, transition, value)
+ sharedValue.lastValue = interruptedValue
+ return interruptedValue
+ }
+
+ private operator fun SharedValue<T, *>.get(scene: SceneKey): T? = targetValues[scene]
+
+ private fun valueOrNull(
+ sharedValue: SharedValue<T, *>,
+ transition: TransitionState.Transition?,
+ ): T? {
+ if (transition == null) {
+ return sharedValue[layoutImpl.state.transitionState.currentScene]
+ }
+
+ val fromValue = sharedValue[transition.fromScene]
+ val toValue = sharedValue[transition.toScene]
+ return if (fromValue != null && toValue != null) {
+ if (fromValue == toValue) {
+ // Optimization: avoid reading progress if the values are the same, so we don't
+ // relayout/redraw for nothing.
+ fromValue
+ } else {
+ // In the case of bouncing, if the value remains constant during the overscroll, we
+ // should use the value of the scene we are bouncing around.
+ if (!canOverflow && transition is TransitionState.HasOverscrollProperties) {
+ val bouncingScene = transition.bouncingScene
+ if (bouncingScene != null) {
+ return sharedValue[bouncingScene]
}
-
- val progress =
- if (canOverflow) transition.progress
- else transition.progress.fastCoerceIn(0f, 1f)
- lerp(fromValue, toValue, progress)
}
- } else fromValue ?: toValue
+
+ val progress =
+ if (canOverflow) transition.progress
+ else transition.progress.fastCoerceIn(0f, 1f)
+ sharedValue.type.lerp(fromValue, toValue, progress)
+ }
+ } else fromValue ?: toValue
+ }
+
+ private fun transition(sharedValue: SharedValue<T, Delta>): TransitionState.Transition? {
+ val targetValues = sharedValue.targetValues
+ val transition =
+ if (element != null) {
+ layoutImpl.elements[element]?.sceneStates?.let { sceneStates ->
+ layoutImpl.state.currentTransitions.fastLastOrNull { transition ->
+ transition.fromScene in sceneStates || transition.toScene in sceneStates
+ }
+ }
+ } else {
+ layoutImpl.state.currentTransitions.fastLastOrNull { transition ->
+ transition.fromScene in targetValues || transition.toScene in targetValues
+ }
+ }
+
+ val previousTransition = sharedValue.lastTransition
+ sharedValue.lastTransition = transition
+
+ if (transition != previousTransition && transition != null && previousTransition != null) {
+ // The previous transition was interrupted by another transition.
+ sharedValue.valueBeforeInterruption = sharedValue.lastValue
+ sharedValue.valueInterruptionDelta = sharedValue.type.zeroDeltaValue
+ } else if (transition == null && previousTransition != null) {
+ // The transition was just finished.
+ sharedValue.valueBeforeInterruption = sharedValue.type.unspecifiedValue
+ sharedValue.valueInterruptionDelta = sharedValue.type.zeroDeltaValue
+ }
+
+ return transition
+ }
+
+ /**
+ * Compute what [value] should be if we take the
+ * [interruption progress][TransitionState.Transition.interruptionProgress] of [transition] into
+ * account.
+ */
+ private fun computeInterruptedValue(
+ sharedValue: SharedValue<T, Delta>,
+ transition: TransitionState.Transition?,
+ value: T,
+ ): T {
+ val type = sharedValue.type
+ if (sharedValue.valueBeforeInterruption != type.unspecifiedValue) {
+ sharedValue.valueInterruptionDelta =
+ type.diff(sharedValue.valueBeforeInterruption, value)
+ sharedValue.valueBeforeInterruption = type.unspecifiedValue
+ }
+
+ val delta = sharedValue.valueInterruptionDelta
+ return if (delta == type.zeroDeltaValue || transition == null) {
+ value
+ } else {
+ val interruptionProgress = transition.interruptionProgress(layoutImpl)
+ if (interruptionProgress == 0f) {
+ value
+ } else {
+ type.addWeighted(value, delta, interruptionProgress)
+ }
}
}
- // TODO(b/311600838): Remove this. We should not have to fallback to the current scene value,
- // but we have to because code of removed nodes can still run if they are placed with a graphics
- // layer.
- ?: sceneValue(scene)
+
+ @Composable
+ override fun unsafeCompositionState(initialValue: T): State<T> {
+ val state = remember { mutableStateOf(initialValue) }
+
+ val animatedState = this
+ LaunchedEffect(animatedState) {
+ snapshotFlow { animatedState.value }.collect { state.value = it }
+ }
+
+ return state
+ }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 1f81245..e9633c2 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -148,9 +148,9 @@
val swipes = computeSwipes(fromScene, startedPosition, pointersDown)
val result =
swipes.findUserActionResult(fromScene, overSlop, true)
- // As we were unable to locate a valid target scene, the initial SwipeTransition
- // cannot be defined. Consequently, a simple NoOp Controller will be returned.
- ?: return NoOpDragController
+ // As we were unable to locate a valid target scene, the initial SwipeTransition
+ // cannot be defined. Consequently, a simple NoOp Controller will be returned.
+ ?: return NoOpDragController
return updateDragController(
swipes = swipes,
@@ -521,6 +521,7 @@
}
return SwipeTransition(
+ layoutImpl = layoutImpl,
layoutState = layoutState,
coroutineScope = coroutineScope,
key = result.transitionKey,
@@ -534,6 +535,7 @@
private fun SwipeTransition(old: SwipeTransition): SwipeTransition {
return SwipeTransition(
+ layoutImpl = old.layoutImpl,
layoutState = old.layoutState,
coroutineScope = old.coroutineScope,
key = old.key,
@@ -550,6 +552,7 @@
}
private class SwipeTransition(
+ val layoutImpl: SceneTransitionLayoutImpl,
val layoutState: BaseSceneTransitionLayoutState,
val coroutineScope: CoroutineScope,
val key: TransitionKey?,
@@ -607,6 +610,12 @@
override val overscrollScope: OverscrollScope =
object : OverscrollScope {
+ override val density: Float
+ get() = layoutImpl.density.density
+
+ override val fontScale: Float
+ get() = layoutImpl.density.fontScale
+
override val absoluteDistance: Float
get() = distance().absoluteValue
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index 4b20aca..be005ea 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -77,7 +77,7 @@
override fun <T> animateElementValueAsState(
value: T,
key: ValueKey,
- lerp: (start: T, stop: T, fraction: Float) -> T,
+ type: SharedValueType<T, *>,
canOverflow: Boolean
): AnimatedState<T> {
return animateSharedValueAsState(
@@ -86,7 +86,7 @@
element,
key,
value,
- lerp,
+ type,
canOverflow,
)
}
@@ -184,8 +184,7 @@
fromSceneZIndex = layoutImpl.scenes.getValue(transition.fromScene).zIndex,
toSceneZIndex = layoutImpl.scenes.getValue(transition.toScene).zIndex,
) != null
- }
- ?: return false
+ } ?: return false
// Always compose movable elements in the scene picked by their scene picker.
return shouldDrawOrComposeSharedElement(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 6fef33c..936f4ba 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -24,7 +24,6 @@
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.platform.testTag
@@ -69,7 +68,6 @@
}
@Composable
- @OptIn(ExperimentalComposeUiApi::class)
fun Content(modifier: Modifier = Modifier) {
Box(
modifier
@@ -96,6 +94,7 @@
private val layoutImpl: SceneTransitionLayoutImpl,
private val scene: Scene,
) : SceneScope, ElementStateScope by layoutImpl.elementStateScope {
+ override val sceneKey: SceneKey = scene.key
override val layoutState: SceneTransitionLayoutState = layoutImpl.state
override fun Modifier.element(key: ElementKey): Modifier {
@@ -124,7 +123,7 @@
override fun <T> animateSceneValueAsState(
value: T,
key: ValueKey,
- lerp: (T, T, Float) -> T,
+ type: SharedValueType<T, *>,
canOverflow: Boolean
): AnimatedState<T> {
return animateSharedValueAsState(
@@ -133,7 +132,7 @@
element = null,
key = key,
value = value,
- lerp = lerp,
+ type = type,
canOverflow = canOverflow,
)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index cf8c584..2946b04 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -167,6 +167,9 @@
@Stable
@ElementDsl
interface BaseSceneScope : ElementStateScope {
+ /** The key of this scene. */
+ val sceneKey: SceneKey
+
/** The state of the [SceneTransitionLayout] in which this scene is contained. */
val layoutState: SceneTransitionLayoutState
@@ -285,9 +288,7 @@
*
* @param value the value of this shared value in the current scene.
* @param key the key of this shared value.
- * @param lerp the *linear* interpolation function that should be used to interpolate between
- * two different values. Note that it has to be linear because the [fraction] passed to this
- * interpolator is already interpolated.
+ * @param type the [SharedValueType] of this animated value.
* @param canOverflow whether this value can overflow past the values it is interpolated
* between, for instance because the transition is animated using a bouncy spring.
* @see animateSceneIntAsState
@@ -299,11 +300,39 @@
fun <T> animateSceneValueAsState(
value: T,
key: ValueKey,
- lerp: (start: T, stop: T, fraction: Float) -> T,
+ type: SharedValueType<T, *>,
canOverflow: Boolean,
): AnimatedState<T>
}
+/**
+ * The type of a shared value animated using [ElementScope.animateElementValueAsState] or
+ * [SceneScope.animateSceneValueAsState].
+ */
+@Stable
+interface SharedValueType<T, Delta> {
+ /** The unspecified value for this type. */
+ val unspecifiedValue: T
+
+ /**
+ * The zero value of this type. It should be equal to what [diff(x, x)] returns for any value of
+ * x.
+ */
+ val zeroDeltaValue: Delta
+
+ /**
+ * Return the linear interpolation of [a] and [b] at the given [progress], i.e. `a + (b - a) *
+ * progress`.
+ */
+ fun lerp(a: T, b: T, progress: Float): T
+
+ /** Return `a - b`. */
+ fun diff(a: T, b: T): Delta
+
+ /** Return `a + b * bWeight`. */
+ fun addWeighted(a: T, b: Delta, bWeight: Float): T
+}
+
@Stable
@ElementDsl
interface ElementScope<ContentScope> {
@@ -312,9 +341,7 @@
*
* @param value the value of this shared value in the current scene.
* @param key the key of this shared value.
- * @param lerp the *linear* interpolation function that should be used to interpolate between
- * two different values. Note that it has to be linear because the [fraction] passed to this
- * interpolator is already interpolated.
+ * @param type the [SharedValueType] of this animated value.
* @param canOverflow whether this value can overflow past the values it is interpolated
* between, for instance because the transition is animated using a bouncy spring.
* @see animateElementIntAsState
@@ -326,7 +353,7 @@
fun <T> animateElementValueAsState(
value: T,
key: ValueKey,
- lerp: (start: T, stop: T, fraction: Float) -> T,
+ type: SharedValueType<T, *>,
canOverflow: Boolean,
): AnimatedState<T>
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index c614265..5fa7c87 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -85,15 +85,14 @@
* The different values of a shared value keyed by a a [ValueKey] and the different elements and
* scenes it is associated to.
*/
- private var _sharedValues:
- MutableMap<ValueKey, MutableMap<ElementKey?, SnapshotStateMap<SceneKey, *>>>? =
+ private var _sharedValues: MutableMap<ValueKey, MutableMap<ElementKey?, SharedValue<*, *>>>? =
null
- internal val sharedValues:
- MutableMap<ValueKey, MutableMap<ElementKey?, SnapshotStateMap<SceneKey, *>>>
+ internal val sharedValues: MutableMap<ValueKey, MutableMap<ElementKey?, SharedValue<*, *>>>
get() =
_sharedValues
- ?: mutableMapOf<ValueKey, MutableMap<ElementKey?, SnapshotStateMap<SceneKey, *>>>()
- .also { _sharedValues = it }
+ ?: mutableMapOf<ValueKey, MutableMap<ElementKey?, SharedValue<*, *>>>().also {
+ _sharedValues = it
+ }
// TODO(b/317958526): Lazily allocate scene gesture handlers the first time they are needed.
private val horizontalDraggableHandler: DraggableHandlerImpl
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index a5b6d24..44affd9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -457,7 +457,7 @@
*/
internal fun startTransition(
transition: TransitionState.Transition,
- transitionKey: TransitionKey?,
+ transitionKey: TransitionKey? = null,
chain: Boolean = true,
) {
checkThread()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index a4682ff..465a410 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -20,6 +20,7 @@
import androidx.compose.animation.core.SpringSpec
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
@@ -192,7 +193,7 @@
)
}
-interface OverscrollScope {
+interface OverscrollScope : Density {
/**
* Return the absolute distance between fromScene and toScene, if available, otherwise
* [DistanceUnspecified].
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index e8854cf..6e8b208 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -32,7 +32,12 @@
import androidx.compose.ui.unit.lerp
import androidx.compose.ui.util.lerp
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
+import com.android.compose.animation.scene.TestScenes.SceneD
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertThrows
import org.junit.Rule
import org.junit.Test
@@ -130,8 +135,8 @@
// The transition lasts 64ms = 4 frames.
spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
},
- fromScene = TestScenes.SceneA,
- toScene = TestScenes.SceneB,
+ fromScene = SceneA,
+ toScene = SceneB,
) {
before {
assertThat(lastValueInFrom).isEqualTo(fromValues)
@@ -189,8 +194,8 @@
// The transition lasts 64ms = 4 frames.
spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
},
- fromScene = TestScenes.SceneA,
- toScene = TestScenes.SceneB,
+ fromScene = SceneA,
+ toScene = SceneB,
) {
before {
assertThat(lastValueInFrom).isEqualTo(fromValues)
@@ -243,8 +248,8 @@
// The transition lasts 64ms = 4 frames.
spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
},
- fromScene = TestScenes.SceneA,
- toScene = TestScenes.SceneB,
+ fromScene = SceneA,
+ toScene = SceneB,
) {
before {
assertThat(lastValueInFrom).isEqualTo(fromValues)
@@ -381,4 +386,61 @@
}
}
}
+
+ @Test
+ fun animatedValueIsUsingLastTransition() = runTest {
+ val state =
+ rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl(SceneA, transitions {}) }
+
+ val foo = ValueKey("foo")
+ val bar = ValueKey("bar")
+ val lastValues = mutableMapOf<ValueKey, MutableMap<SceneKey, Float>>()
+
+ @Composable
+ fun SceneScope.animateFloat(value: Float, key: ValueKey) {
+ val animatedValue = animateSceneFloatAsState(value, key)
+ LaunchedEffect(animatedValue) {
+ snapshotFlow { animatedValue.value }
+ .collect { lastValues.getOrPut(key) { mutableMapOf() }[sceneKey] = it }
+ }
+ }
+
+ rule.setContent {
+ SceneTransitionLayout(state) {
+ // foo goes from 0f to 100f in A => B.
+ scene(SceneA) { animateFloat(0f, foo) }
+ scene(SceneB) { animateFloat(100f, foo) }
+
+ // bar goes from 0f to 10f in C => D.
+ scene(SceneC) { animateFloat(0f, bar) }
+ scene(SceneD) { animateFloat(10f, bar) }
+ }
+ }
+
+ rule.runOnUiThread {
+ // A => B is at 30%.
+ state.startTransition(
+ transition(
+ from = SceneA,
+ to = SceneB,
+ progress = { 0.3f },
+ onFinish = neverFinish(),
+ )
+ )
+
+ // C => D is at 70%.
+ state.startTransition(transition(from = SceneC, to = SceneD, progress = { 0.7f }))
+ }
+ rule.waitForIdle()
+
+ assertThat(lastValues[foo]?.get(SceneA)).isWithin(0.001f).of(30f)
+ assertThat(lastValues[foo]?.get(SceneB)).isWithin(0.001f).of(30f)
+ assertThat(lastValues[foo]?.get(SceneC)).isNull()
+ assertThat(lastValues[foo]?.get(SceneD)).isNull()
+
+ assertThat(lastValues[bar]?.get(SceneA)).isNull()
+ assertThat(lastValues[bar]?.get(SceneB)).isNull()
+ assertThat(lastValues[bar]?.get(SceneC)).isWithin(0.001f).of(7f)
+ assertThat(lastValues[bar]?.get(SceneD)).isWithin(0.001f).of(7f)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 9692fae..beb74bc 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -59,6 +59,7 @@
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.lerp
+import androidx.compose.ui.util.lerp
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
@@ -348,7 +349,7 @@
),
onLayoutImpl = { nullableLayoutImpl = it },
) {
- scene(SceneA) { /* Nothing */}
+ scene(SceneA) { /* Nothing */ }
scene(SceneB) { Box(Modifier.element(key)) }
scene(SceneC) {
when (sceneCState) {
@@ -1083,10 +1084,17 @@
}
val layoutSize = DpSize(200.dp, 100.dp)
+ val lastValues = mutableMapOf<SceneKey, Float>()
@Composable
- fun SceneScope.Foo(size: Dp, modifier: Modifier = Modifier) {
- Box(modifier.element(TestElements.Foo).size(size))
+ fun SceneScope.Foo(size: Dp, value: Float, modifier: Modifier = Modifier) {
+ val sceneKey = this.sceneKey
+ Element(TestElements.Foo, modifier.size(size)) {
+ val animatedValue = animateElementFloatAsState(value, TestValues.Value1)
+ LaunchedEffect(animatedValue) {
+ snapshotFlow { animatedValue.value }.collect { lastValues[sceneKey] = it }
+ }
+ }
}
// The size of Foo when idle in A, B or C.
@@ -1094,6 +1102,11 @@
val sizeInB = 30.dp
val sizeInC = 50.dp
+ // The target value when idle in A, B, or C.
+ val valueInA = 0f
+ val valueInB = 100f
+ val valueInC = 200f
+
lateinit var layoutImpl: SceneTransitionLayoutImpl
rule.setContent {
SceneTransitionLayoutForTesting(
@@ -1103,7 +1116,9 @@
) {
// In scene A, Foo is aligned at the TopStart.
scene(SceneA) {
- Box(Modifier.fillMaxSize()) { Foo(sizeInA, Modifier.align(Alignment.TopStart)) }
+ Box(Modifier.fillMaxSize()) {
+ Foo(sizeInA, valueInA, Modifier.align(Alignment.TopStart))
+ }
}
// In scene C, Foo is aligned at the BottomEnd, so it moves vertically when coming
@@ -1111,14 +1126,16 @@
// values and deltas are properly cleared once all transitions are done.
scene(SceneC) {
Box(Modifier.fillMaxSize()) {
- Foo(sizeInC, Modifier.align(Alignment.BottomEnd))
+ Foo(sizeInC, valueInC, Modifier.align(Alignment.BottomEnd))
}
}
// In scene B, Foo is aligned at the TopEnd, so it moves horizontally when coming
// from A.
scene(SceneB) {
- Box(Modifier.fillMaxSize()) { Foo(sizeInB, Modifier.align(Alignment.TopEnd)) }
+ Box(Modifier.fillMaxSize()) {
+ Foo(sizeInB, valueInB, Modifier.align(Alignment.TopEnd))
+ }
}
}
}
@@ -1134,6 +1151,10 @@
.assertSizeIsEqualTo(sizeInA)
.assertPositionInRootIsEqualTo(offsetInA.x, offsetInA.y)
+ assertThat(lastValues[SceneA]).isWithin(0.001f).of(valueInA)
+ assertThat(lastValues[SceneB]).isNull()
+ assertThat(lastValues[SceneC]).isNull()
+
// Current transition is A => B at 50%.
val aToBProgress = 0.5f
val aToB =
@@ -1145,12 +1166,17 @@
)
val offsetInAToB = lerp(offsetInA, offsetInB, aToBProgress)
val sizeInAToB = lerp(sizeInA, sizeInB, aToBProgress)
+ val valueInAToB = lerp(valueInA, valueInB, aToBProgress)
rule.runOnUiThread { state.startTransition(aToB, transitionKey = null) }
rule
.onNode(isElement(TestElements.Foo, SceneB))
.assertSizeIsEqualTo(sizeInAToB)
.assertPositionInRootIsEqualTo(offsetInAToB.x, offsetInAToB.y)
+ assertThat(lastValues[SceneA]).isWithin(0.001f).of(valueInAToB)
+ assertThat(lastValues[SceneB]).isWithin(0.001f).of(valueInAToB)
+ assertThat(lastValues[SceneC]).isNull()
+
// Start B => C at 0%.
var bToCProgress by mutableFloatStateOf(0f)
var interruptionProgress by mutableFloatStateOf(1f)
@@ -1167,6 +1193,11 @@
// to the current transition offset and size.
val offsetInterruptionDelta = offsetInAToB - offsetInB
val sizeInterruptionDelta = sizeInAToB - sizeInB
+ val valueInterruptionDelta = valueInAToB - valueInB
+
+ assertThat(offsetInterruptionDelta).isNotEqualTo(DpOffset.Zero)
+ assertThat(sizeInterruptionDelta).isNotEqualTo(0.dp)
+ assertThat(valueInterruptionDelta).isNotEqualTo(0f)
// Interruption progress is at 100% and bToC is at 0%, so Foo should be at the same offset
// and size as right before the interruption.
@@ -1175,11 +1206,16 @@
.assertPositionInRootIsEqualTo(offsetInAToB.x, offsetInAToB.y)
.assertSizeIsEqualTo(sizeInAToB)
+ assertThat(lastValues[SceneA]).isWithin(0.001f).of(valueInAToB)
+ assertThat(lastValues[SceneB]).isWithin(0.001f).of(valueInAToB)
+ assertThat(lastValues[SceneC]).isWithin(0.001f).of(valueInAToB)
+
// Move the transition forward at 30% and set the interruption progress to 50%.
bToCProgress = 0.3f
interruptionProgress = 0.5f
val offsetInBToC = lerp(offsetInB, offsetInC, bToCProgress)
val sizeInBToC = lerp(sizeInB, sizeInC, bToCProgress)
+ val valueInBToC = lerp(valueInB, valueInC, bToCProgress)
val offsetInBToCWithInterruption =
offsetInBToC +
DpOffset(
@@ -1187,6 +1223,9 @@
offsetInterruptionDelta.y * interruptionProgress,
)
val sizeInBToCWithInterruption = sizeInBToC + sizeInterruptionDelta * interruptionProgress
+ val valueInBToCWithInterruption =
+ valueInBToC + valueInterruptionDelta * interruptionProgress
+
rule.waitForIdle()
rule
.onNode(isElement(TestElements.Foo, SceneB))
@@ -1196,6 +1235,10 @@
)
.assertSizeIsEqualTo(sizeInBToCWithInterruption)
+ assertThat(lastValues[SceneA]).isWithin(0.001f).of(valueInBToCWithInterruption)
+ assertThat(lastValues[SceneB]).isWithin(0.001f).of(valueInBToCWithInterruption)
+ assertThat(lastValues[SceneC]).isWithin(0.001f).of(valueInBToCWithInterruption)
+
// Finish the transition and interruption.
bToCProgress = 1f
interruptionProgress = 0f
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 3751a22..08532bd 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -461,4 +461,30 @@
assertThat(exception).hasMessageThat().contains(Back.toString())
assertThat(exception).hasMessageThat().contains(SceneA.debugName)
}
+
+ @Test
+ fun sceneKeyInScope() {
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+
+ var keyInA: SceneKey? = null
+ var keyInB: SceneKey? = null
+ var keyInC: SceneKey? = null
+ rule.setContent {
+ SceneTransitionLayout(state) {
+ scene(SceneA) { keyInA = sceneKey }
+ scene(SceneB) { keyInB = sceneKey }
+ scene(SceneC) { keyInC = sceneKey }
+ }
+ }
+
+ // Snap to B then C to compose these scenes at least once.
+ rule.runOnUiThread { state.snapToScene(SceneB) }
+ rule.waitForIdle()
+ rule.runOnUiThread { state.snapToScene(SceneC) }
+ rule.waitForIdle()
+
+ assertThat(keyInA).isEqualTo(SceneA)
+ assertThat(keyInB).isEqualTo(SceneB)
+ assertThat(keyInC).isEqualTo(SceneC)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 3a806a4..25ea2ee 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -30,6 +30,7 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performTouchInput
@@ -594,4 +595,43 @@
assertThat(transition).hasToScene(SceneB)
assertThat(transition).hasProgress(0.5f, tolerance = 0.01f)
}
+
+ @Test
+ fun overscrollScopeExtendsDensity() {
+ val swipeDistance = 100.dp
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ SceneA,
+ transitions {
+ from(SceneA, to = SceneB) { distance = FixedDistance(swipeDistance) }
+
+ overscroll(SceneB, Orientation.Vertical) {
+ translate(TestElements.Foo, x = { 20.dp.toPx() }, y = { 30.dp.toPx() })
+ }
+ }
+ )
+ }
+ val layoutSize = 200.dp
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+ scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+ Box(Modifier.fillMaxSize())
+ }
+ scene(SceneB) { Box(Modifier.element(TestElements.Foo).fillMaxSize()) }
+ }
+ }
+
+ // Swipe down by twice the swipe distance so that we are at 100% overscrolling on scene B.
+ rule.onRoot().performTouchInput {
+ val middle = (layoutSize / 2).toPx()
+ down(Offset(middle, middle))
+ moveBy(Offset(0f, touchSlop + (swipeDistance * 2).toPx()), delayMillis = 1_000)
+ }
+
+ // Foo should be translated by (20dp, 30dp).
+ rule.onNode(isElement(TestElements.Foo)).assertPositionInRootIsEqualTo(20.dp, 30.dp)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
index a609be4..e6fa69d 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
@@ -41,8 +41,10 @@
return object : TransitionState.Transition(from, to), TransitionState.HasOverscrollProperties {
override val currentScene: SceneKey
get() = current()
+
override val progress: Float
get() = progress()
+
override val progressVelocity: Float
get() = progressVelocity()
@@ -53,6 +55,8 @@
override val orientation: Orientation = orientation
override val overscrollScope: OverscrollScope =
object : OverscrollScope {
+ override val density: Float = 1f
+ override val fontScale: Float = 1f
override val absoluteDistance = 0f
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index b392014..502dbe3 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -239,6 +239,8 @@
}
inner class DefaultClockEvents : ClockEvents {
+ override var isReactiveTouchInteractionEnabled: Boolean = false
+
override fun onTimeFormatChanged(is24Hr: Boolean) =
clocks.forEach { it.refreshFormat(is24Hr) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
index 89c5495..fb2b33d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
@@ -34,6 +34,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.communal.data.model.DisabledReason
+import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_BACKGROUND_SETTING
+import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -43,6 +45,7 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -216,6 +219,32 @@
)
}
+ @Test
+ fun backgroundType_defaultValue() =
+ testScope.runTest {
+ val backgroundType by collectLastValue(underTest.getBackground(PRIMARY_USER))
+ assertThat(backgroundType).isEqualTo(CommunalBackgroundType.DEFAULT)
+ }
+
+ @Test
+ fun backgroundType_verifyAllValues() =
+ testScope.runTest {
+ val backgroundType by collectLastValue(underTest.getBackground(PRIMARY_USER))
+ for (type in CommunalBackgroundType.entries) {
+ kosmos.fakeSettings.putIntForUser(
+ GLANCEABLE_HUB_BACKGROUND_SETTING,
+ type.value,
+ PRIMARY_USER.id
+ )
+ assertWithMessage(
+ "Expected $type when $GLANCEABLE_HUB_BACKGROUND_SETTING is set to" +
+ " ${type.value} but was $backgroundType"
+ )
+ .that(backgroundType)
+ .isEqualTo(type)
+ }
+ }
+
private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
.thenReturn(disabledFlags)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 5e19a41..9dcea82 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -37,6 +37,7 @@
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.domain.interactor.communalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -54,6 +55,8 @@
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -62,6 +65,8 @@
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.shade.ShadeTestUtil
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -71,7 +76,6 @@
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
@@ -85,6 +89,7 @@
import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -138,6 +143,8 @@
)
whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id))
+ kosmos.powerInteractor.setAwakeForTest()
+
underTest =
CommunalViewModel(
testScope,
@@ -146,6 +153,7 @@
kosmos.keyguardInteractor,
kosmos.communalSceneInteractor,
kosmos.communalInteractor,
+ kosmos.communalSettingsInteractor,
kosmos.communalTutorialInteractor,
kosmos.shadeInteractor,
mediaHost,
@@ -468,6 +476,229 @@
assertThat(isFocusable).isEqualTo(false)
}
+ @Test
+ fun isCommunalContentFlowFrozen_whenActivityStartedWhileDreaming() =
+ testScope.runTest {
+ val isCommunalContentFlowFrozen by
+ collectLastValue(underTest.isCommunalContentFlowFrozen)
+
+ // 1. When dreaming not dozing
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+ )
+ keyguardRepository.setDreaming(true)
+ keyguardRepository.setDreamingWithOverlay(true)
+ advanceTimeBy(60L)
+ // And keyguard is occluded by dream
+ keyguardRepository.setKeyguardOccluded(true)
+
+ // And on hub
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+
+ // Then flow is not frozen
+ assertThat(isCommunalContentFlowFrozen).isEqualTo(false)
+
+ // 2. When dreaming stopped by the new activity about to show on lock screen
+ keyguardRepository.setDreamingWithOverlay(false)
+ advanceTimeBy(60L)
+
+ // Then flow is frozen
+ assertThat(isCommunalContentFlowFrozen).isEqualTo(true)
+
+ // 3. When transitioned to OCCLUDED and activity shows
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ testScope = testScope,
+ )
+
+ // Then flow is not frozen
+ assertThat(isCommunalContentFlowFrozen).isEqualTo(false)
+ }
+
+ @Test
+ fun isCommunalContentFlowFrozen_whenActivityStartedInHandheldMode() =
+ testScope.runTest {
+ val isCommunalContentFlowFrozen by
+ collectLastValue(underTest.isCommunalContentFlowFrozen)
+
+ // 1. When on keyguard and not occluded
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setKeyguardOccluded(false)
+
+ // And transitioned to hub
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+
+ // Then flow is not frozen
+ assertThat(isCommunalContentFlowFrozen).isEqualTo(false)
+
+ // 2. When occluded by a new activity
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+
+ // And transitioning to occluded
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+
+ keyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ transitionState = TransitionState.RUNNING,
+ value = 0.5f,
+ )
+
+ // Then flow is frozen
+ assertThat(isCommunalContentFlowFrozen).isEqualTo(true)
+
+ // 3. When transition is finished
+ keyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ transitionState = TransitionState.FINISHED,
+ value = 1f,
+ )
+
+ // Then flow is not frozen
+ assertThat(isCommunalContentFlowFrozen).isEqualTo(false)
+ }
+
+ @Test
+ fun communalContent_emitsFrozenContent_whenFrozen() =
+ testScope.runTest {
+ val communalContent by collectLastValue(underTest.communalContent)
+ tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+ // When dreaming
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+ )
+ keyguardRepository.setDreaming(true)
+ keyguardRepository.setDreamingWithOverlay(true)
+ advanceTimeBy(60L)
+ keyguardRepository.setKeyguardOccluded(true)
+
+ // And transitioned to hub
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+
+ // Widgets available
+ val widgets =
+ listOf(
+ CommunalWidgetContentModel.Available(
+ appWidgetId = 0,
+ priority = 30,
+ providerInfo = providerInfo,
+ ),
+ CommunalWidgetContentModel.Available(
+ appWidgetId = 1,
+ priority = 20,
+ providerInfo = providerInfo,
+ ),
+ )
+ widgetRepository.setCommunalWidgets(widgets)
+
+ // Then hub shows widgets and the CTA tile
+ assertThat(communalContent).hasSize(3)
+
+ // When dreaming stopped by another activity which should freeze flow
+ keyguardRepository.setDreamingWithOverlay(false)
+ advanceTimeBy(60L)
+
+ // New timer available
+ val target = Mockito.mock(SmartspaceTarget::class.java)
+ whenever<String?>(target.smartspaceTargetId).thenReturn("target")
+ whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+ whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java))
+ smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
+ runCurrent()
+
+ // Still only emits widgets and the CTA tile
+ assertThat(communalContent).hasSize(3)
+ assertThat(communalContent?.get(0))
+ .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
+ assertThat(communalContent?.get(1))
+ .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
+ assertThat(communalContent?.get(2))
+ .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
+ }
+
+ @Test
+ fun communalContent_emitsLatestContent_whenNotFrozen() =
+ testScope.runTest {
+ val communalContent by collectLastValue(underTest.communalContent)
+ tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+ // When dreaming
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+ )
+ keyguardRepository.setDreaming(true)
+ keyguardRepository.setDreamingWithOverlay(true)
+ advanceTimeBy(60L)
+ keyguardRepository.setKeyguardOccluded(true)
+
+ // Transitioned to Glanceable hub.
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+
+ // And widgets available
+ val widgets =
+ listOf(
+ CommunalWidgetContentModel.Available(
+ appWidgetId = 0,
+ priority = 30,
+ providerInfo = providerInfo,
+ ),
+ CommunalWidgetContentModel.Available(
+ appWidgetId = 1,
+ priority = 20,
+ providerInfo = providerInfo,
+ ),
+ )
+ widgetRepository.setCommunalWidgets(widgets)
+
+ // Then emits widgets and the CTA tile
+ assertThat(communalContent).hasSize(3)
+
+ // When new timer available
+ val target = Mockito.mock(SmartspaceTarget::class.java)
+ whenever(target.smartspaceTargetId).thenReturn("target")
+ whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+ whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java))
+ smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
+ runCurrent()
+
+ // Then emits timer, widgets and the CTA tile
+ assertThat(communalContent).hasSize(4)
+ assertThat(communalContent?.get(0))
+ .isInstanceOf(CommunalContentModel.Smartspace::class.java)
+ assertThat(communalContent?.get(1))
+ .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
+ assertThat(communalContent?.get(2))
+ .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
+ assertThat(communalContent?.get(3))
+ .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
+ }
+
private suspend fun setIsMainUser(isMainUser: Boolean) {
whenever(user.isMain).thenReturn(isMainUser)
userRepository.setUserInfos(listOf(user))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
index c51413a..4849e66 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.qsTileFactory
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.TestScope
@@ -41,6 +42,7 @@
private val kosmos = testKosmos()
private val vibratorHelper = kosmos.vibratorHelper
+ private val qsTile = kosmos.qsTileFactory.createTile("Test Tile")
private val effectDuration = 400
private val lowTickDuration = 12
@@ -61,6 +63,7 @@
vibratorHelper,
kosmos.keyguardInteractor,
)
+ longPressEffect.qsTile = qsTile
}
@Test
@@ -91,8 +94,10 @@
// GIVEN an action down event occurs
longPressEffect.handleActionDown()
- // THEN the effect moves to the TIMEOUT_WAIT state
+ // THEN the effect moves to the TIMEOUT_WAIT state and starts the wait
+ val action by collectLastValue(longPressEffect.actionType)
assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
+ assertThat(action).isEqualTo(QSLongPressEffect.ActionType.WAIT_TAP_TIMEOUT)
}
@Test
@@ -107,20 +112,6 @@
}
@Test
- fun onActionUp_whileWaiting_performsClick() =
- testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
- // GIVEN an action is being collected
- val action by collectLastValue(longPressEffect.actionType)
-
- // GIVEN an action up occurs
- longPressEffect.handleActionUp()
-
- // THEN the action to invoke is the click action and the effect does not start
- assertThat(action).isEqualTo(QSLongPressEffect.ActionType.CLICK)
- assertEffectDidNotStart()
- }
-
- @Test
fun onWaitComplete_whileWaiting_beginsEffect() =
testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
// GIVEN the pressed timeout is complete
@@ -221,8 +212,10 @@
// GIVEN that the animator was cancelled
longPressEffect.handleAnimationCancel()
- // THEN the state goes to the timeout wait
+ // THEN the state goes to the timeout wait and the wait is posted
+ val action by collectLastValue(longPressEffect.actionType)
assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
+ assertThat(action).isEqualTo(QSLongPressEffect.ActionType.WAIT_TAP_TIMEOUT)
}
@Test
@@ -238,6 +231,29 @@
assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
}
+ @Test
+ fun onTileClick_whileWaiting_withQSTile_clicks() =
+ testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
+ // GIVEN that a click was detected
+ val couldClick = longPressEffect.onTileClick()
+
+ // THEN the click is successful
+ assertThat(couldClick).isTrue()
+ }
+
+ @Test
+ fun onTileClick_whileWaiting_withoutQSTile_cannotClick() =
+ testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
+ // GIVEN that no QSTile has been set
+ longPressEffect.qsTile = null
+
+ // GIVEN that a click was detected
+ val couldClick = longPressEffect.onTileClick()
+
+ // THEN the click is not successful
+ assertThat(couldClick).isFalse()
+ }
+
private fun testWithScope(initialize: Boolean = true, test: suspend TestScope.() -> Unit) =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 5068f68..78a1167 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -350,7 +350,7 @@
}
@Test
- fun quickAffordance_doNotSendUpdatesWhileShadeExpandingAndStillHidden() =
+ fun quickAffordance_updateOncePerShadeExpansion() =
testScope.runTest {
val shadeExpansion = MutableStateFlow(0f)
whenever(shadeInteractor.anyExpansion).thenReturn(shadeExpansion)
@@ -365,9 +365,7 @@
shadeExpansion.value = i / 10f
}
- assertThat(collectedValue[0])
- .isInstanceOf(KeyguardQuickAffordanceModel.Hidden::class.java)
- assertThat(collectedValue.size).isEqualTo(initialSize)
+ assertThat(collectedValue.size).isEqualTo(initialSize + 1)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 82e2bb7..c35c165 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -41,6 +41,8 @@
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -53,6 +55,7 @@
import com.android.systemui.res.R
import com.android.systemui.shade.mockLargeScreenHeaderHelper
import com.android.systemui.shade.shadeTestUtil
+import com.android.systemui.statusbar.notification.NotificationUtils.interpolate
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
@@ -794,11 +797,47 @@
@DisableSceneContainer
fun updateBounds_fromKeyguardRoot() =
testScope.runTest {
- val bounds by collectLastValue(underTest.bounds)
+ val startProgress = 0f
+ val startStep = TransitionStep(LOCKSCREEN, AOD, startProgress, TransitionState.STARTED)
+ val boundsChangingProgress = 0.2f
+ val boundsChangingStep =
+ TransitionStep(LOCKSCREEN, AOD, boundsChangingProgress, TransitionState.RUNNING)
+ val boundsInterpolatingProgress = 0.6f
+ val boundsInterpolatingStep =
+ TransitionStep(
+ LOCKSCREEN,
+ AOD,
+ boundsInterpolatingProgress,
+ TransitionState.RUNNING
+ )
+ val finishProgress = 1.0f
+ val finishStep =
+ TransitionStep(LOCKSCREEN, AOD, finishProgress, TransitionState.FINISHED)
+ val bounds by collectLastValue(underTest.bounds)
val top = 123f
val bottom = 456f
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(startStep)
+ runCurrent()
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(boundsChangingStep)
+ runCurrent()
keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom)
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(boundsInterpolatingStep)
+ runCurrent()
+ val adjustedProgress =
+ (boundsInterpolatingProgress - boundsChangingProgress) /
+ (1 - boundsChangingProgress)
+ val interpolatedTop = interpolate(0f, top, adjustedProgress)
+ val interpolatedBottom = interpolate(0f, bottom, adjustedProgress)
+ assertThat(bounds)
+ .isEqualTo(
+ NotificationContainerBounds(top = interpolatedTop, bottom = interpolatedBottom)
+ )
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(finishStep)
+ runCurrent()
assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom))
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
index b83b93b..10a4eb7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
@@ -50,6 +50,8 @@
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
+private const val builtInDeviceName = "This phone"
+
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@SmallTest
@@ -71,6 +73,10 @@
addOverride(R.drawable.ic_media_speaker_device, testIcon)
addOverride(com.android.internal.R.drawable.ic_bt_hearing_aid, testIcon)
+
+ addOverride(R.string.media_transfer_this_device_name_tv, builtInDeviceName)
+ addOverride(R.string.media_transfer_this_device_name_tablet, builtInDeviceName)
+ addOverride(R.string.media_transfer_this_device_name, builtInDeviceName)
}
}
}
@@ -90,7 +96,7 @@
assertThat(device).isInstanceOf(AudioOutputDevice.BuiltIn::class.java)
assertThat(device!!.icon).isEqualTo(testIcon)
- assertThat(device!!.name).isEqualTo("built_in")
+ assertThat(device!!.name).isEqualTo(builtInDeviceName)
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt
new file mode 100644
index 0000000..8921a23
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt
@@ -0,0 +1,141 @@
+/*
+ * 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.panel.component.mediaoutput.domain.interactor
+
+import android.graphics.drawable.TestStubDrawable
+import android.media.AudioManager
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.data.repository.TestAudioDevicesFactory
+import com.android.systemui.volume.data.repository.audioRepository
+import com.android.systemui.volume.data.repository.audioSharingRepository
+import com.android.systemui.volume.domain.model.AudioOutputDevice
+import com.android.systemui.volume.localMediaController
+import com.android.systemui.volume.localMediaRepository
+import com.android.systemui.volume.mediaControllerRepository
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaOutputComponentModel
+import com.android.systemui.volume.panel.shared.model.filterData
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val builtInDeviceName = "This phone"
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class MediaOutputComponentInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: MediaOutputComponentInteractor
+
+ @Before
+ fun setUp() =
+ with(kosmos) {
+ audioRepository.setMode(AudioManager.MODE_NORMAL)
+ localMediaRepository.updateCurrentConnectedDevice(
+ TestMediaDevicesFactory.builtInMediaDevice(deviceIcon = testIcon)
+ )
+
+ with(context.orCreateTestableResources) {
+ addOverride(R.drawable.ic_smartphone, testIcon)
+
+ addOverride(R.string.media_transfer_this_device_name_tv, builtInDeviceName)
+ addOverride(R.string.media_transfer_this_device_name_tablet, builtInDeviceName)
+ addOverride(R.string.media_transfer_this_device_name, builtInDeviceName)
+ }
+
+ underTest = mediaOutputComponentInteractor
+ }
+
+ @Test
+ fun inCall_stateIs_Calling() =
+ with(kosmos) {
+ testScope.runTest {
+ with(audioRepository) {
+ setMode(AudioManager.MODE_IN_CALL)
+ setCommunicationDevice(TestAudioDevicesFactory.builtInDevice())
+ }
+
+ val model by collectLastValue(underTest.mediaOutputModel.filterData())
+ runCurrent()
+
+ assertThat(model)
+ .isEqualTo(
+ MediaOutputComponentModel.Calling(
+ AudioOutputDevice.BuiltIn(builtInDeviceName, testIcon),
+ false,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun hasSession_stateIs_MediaSession() =
+ with(kosmos) {
+ testScope.runTest {
+ mediaControllerRepository.setActiveSessions(listOf(localMediaController))
+
+ val model by collectLastValue(underTest.mediaOutputModel.filterData())
+ runCurrent()
+
+ with(model as MediaOutputComponentModel.MediaSession) {
+ assertThat(session.appLabel).isEqualTo("local_media_controller_label")
+ assertThat(session.packageName).isEqualTo("local.test.pkg")
+ assertThat(session.canAdjustVolume).isTrue()
+ assertThat(device)
+ .isEqualTo(AudioOutputDevice.BuiltIn("built_in_media", testIcon))
+ assertThat(isInAudioSharing).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun noMediaOrCall_stateIs_Idle() =
+ with(kosmos) {
+ testScope.runTest {
+ audioSharingRepository.setInAudioSharing(true)
+
+ val model by collectLastValue(underTest.mediaOutputModel.filterData())
+ runCurrent()
+
+ assertThat(model)
+ .isEqualTo(
+ MediaOutputComponentModel.Idle(
+ AudioOutputDevice.BuiltIn("built_in_media", testIcon),
+ true,
+ )
+ )
+ }
+ }
+
+ private companion object {
+ val testIcon = TestStubDrawable()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
index d497b4a..86a20dc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
@@ -31,14 +31,11 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.volume.data.repository.audioSharingRepository
-import com.android.systemui.volume.domain.interactor.audioModeInteractor
-import com.android.systemui.volume.domain.interactor.audioOutputInteractor
import com.android.systemui.volume.localMediaController
import com.android.systemui.volume.localMediaRepository
import com.android.systemui.volume.mediaControllerRepository
-import com.android.systemui.volume.mediaDeviceSessionInteractor
import com.android.systemui.volume.mediaOutputActionsInteractor
-import com.android.systemui.volume.mediaOutputInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.mediaOutputComponentInteractor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
@@ -66,10 +63,7 @@
applicationContext,
testScope.backgroundScope,
mediaOutputActionsInteractor,
- mediaDeviceSessionInteractor,
- audioOutputInteractor,
- audioModeInteractor,
- mediaOutputInteractor,
+ mediaOutputComponentInteractor,
uiEventLogger,
)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 629c96c..c7998f0 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -153,6 +153,9 @@
/** Events that should call when various rendering parameters change */
interface ClockEvents {
+ /** Set to enable or disable swipe interaction */
+ var isReactiveTouchInteractionEnabled: Boolean
+
/** Call whenever timezone changes */
fun onTimeZoneChanged(timeZone: TimeZone)
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_spatial_speaker.xml b/packages/SystemUI/res-keyguard/drawable/ic_spatial_speaker.xml
new file mode 100644
index 0000000..82b222e7
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_spatial_speaker.xml
@@ -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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M360,880q-134,0 -227,-93T40,560h80q0,100 70,170t170,70v80ZM360,740q-75,0 -127.5,-52.5T180,560h80q0,42 29,71t71,29v80ZM400,600q-33,0 -56.5,-23.5T320,520v-320q0,-33 23.5,-56.5T400,120h160q33,0 56.5,23.5T640,200v320q0,33 -23.5,56.5T560,600L400,600ZM400,520h160v-320L400,200v320ZM600,740v-80q42,0 71,-29t29,-71h80q0,75 -52.5,127.5T600,740ZM600,880v-80q100,0 170,-70t70,-170h80q0,134 -93,227T600,880ZM400,520h160,-160Z" />
+</vector>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
index 1366226..e8eb53f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
@@ -17,6 +17,7 @@
package com.android.systemui.flags
import android.app.Activity
+import android.content.pm.PackageManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -35,6 +36,7 @@
) : FlagListenable {
companion object {
const val RECEIVING_PACKAGE = "com.android.systemui"
+ const val RECEIVING_PACKAGE_WATCH = "com.google.android.apps.wearable.systemui"
const val ACTION_SET_FLAG = "com.android.systemui.action.SET_FLAG"
const val ACTION_GET_FLAGS = "com.android.systemui.action.GET_FLAGS"
const val FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS"
@@ -62,7 +64,7 @@
fun getFlagsFuture(): ListenableFuture<Collection<Flag<*>>> {
val intent = Intent(ACTION_GET_FLAGS)
- intent.setPackage(RECEIVING_PACKAGE)
+ intent.setPackage(if (isWatch()) RECEIVING_PACKAGE_WATCH else RECEIVING_PACKAGE)
return CallbackToFutureAdapter.getFuture {
completer: CallbackToFutureAdapter.Completer<Collection<Flag<*>>> ->
@@ -193,6 +195,10 @@
restartAction?.accept(suppressRestart)
}
+ private fun isWatch(): Boolean {
+ return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+ }
+
private data class PerFlagListener(val name: String, val listener: FlagListenable.Listener)
}
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index 3bf3fb3..b116e29 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -308,7 +308,13 @@
} else {
// Don't listen and clear out the text when the device isn't a phone.
mMainExecutor.execute(() -> callback.updateCarrierInfo(
- new CarrierTextCallbackInfo("", null, false, null)
+ new CarrierTextCallbackInfo(
+ /* carrierText= */ "",
+ /* listOfCarriers= */ null,
+ /* anySimReady= */ false,
+ /* isInSatelliteMode= */ false,
+ /* subscriptionIds= */ null,
+ /* airplaneMode= */ false)
));
}
} else {
@@ -448,10 +454,12 @@
displayText = currentSatelliteText;
}
+ boolean isInSatelliteMode = mSatelliteCarrierText != null;
final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
displayText,
carrierNames,
!allSimsMissing,
+ isInSatelliteMode,
subsIds,
airplaneMode);
mLogger.logCallbackSentFromUpdate(info);
@@ -757,21 +765,35 @@
public final CharSequence carrierText;
public final CharSequence[] listOfCarriers;
public final boolean anySimReady;
+ public final boolean isInSatelliteMode;
public final int[] subscriptionIds;
public boolean airplaneMode;
@VisibleForTesting
- public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
- boolean anySimReady, int[] subscriptionIds) {
- this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false);
+ public CarrierTextCallbackInfo(
+ CharSequence carrierText,
+ CharSequence[] listOfCarriers,
+ boolean anySimReady,
+ int[] subscriptionIds) {
+ this(carrierText,
+ listOfCarriers,
+ anySimReady,
+ /* isInSatelliteMode= */ false,
+ subscriptionIds,
+ /* airplaneMode= */ false);
}
- @VisibleForTesting
- public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
- boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) {
+ public CarrierTextCallbackInfo(
+ CharSequence carrierText,
+ CharSequence[] listOfCarriers,
+ boolean anySimReady,
+ boolean isInSatelliteMode,
+ int[] subscriptionIds,
+ boolean airplaneMode) {
this.carrierText = carrierText;
this.listOfCarriers = listOfCarriers;
this.anySimReady = anySimReady;
+ this.isInSatelliteMode = isInSatelliteMode;
this.subscriptionIds = subscriptionIds;
this.airplaneMode = airplaneMode;
}
@@ -782,6 +804,7 @@
+ "carrierText=" + carrierText
+ ", listOfCarriers=" + Arrays.toString(listOfCarriers)
+ ", anySimReady=" + anySimReady
+ + ", isInSatelliteMode=" + isInSatelliteMode
+ ", subscriptionIds=" + Arrays.toString(subscriptionIds)
+ ", airplaneMode=" + airplaneMode
+ '}';
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 47e4b49..f688d4f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -308,14 +308,14 @@
mStatusArea = mView.findViewById(R.id.keyguard_status_area);
mBgExecutor.execute(() -> {
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
false, /* notifyForDescendants */
mDoubleLineClockObserver,
UserHandle.USER_ALL
);
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED,
false, /* notifyForDescendants */
mShowWeatherObserver,
@@ -372,8 +372,8 @@
setClock(null);
mBgExecutor.execute(() -> {
- mSecureSettings.unregisterContentObserver(mDoubleLineClockObserver);
- mSecureSettings.unregisterContentObserver(mShowWeatherObserver);
+ mSecureSettings.unregisterContentObserverSync(mDoubleLineClockObserver);
+ mSecureSettings.unregisterContentObserverSync(mShowWeatherObserver);
});
mKeyguardUnlockAnimationController.removeKeyguardUnlockAnimationListener(
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index ca24ccb..f2a68a8 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -319,7 +319,7 @@
}
// Unregister observer before removing view
- mSecureSettings.unregisterContentObserver(mMagnificationCapabilityObserver);
+ mSecureSettings.unregisterContentObserverSync(mMagnificationCapabilityObserver);
mWindowManager.removeView(mSettingView);
mIsVisible = false;
if (resetPosition) {
@@ -380,7 +380,7 @@
mWindowManager.addView(mSettingView, mParams);
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
mMagnificationCapabilityObserver,
UserHandle.USER_CURRENT);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
index eb840f1..ffb5f3d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
@@ -240,26 +240,26 @@
}
void registerObserversAndCallbacks() {
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
mSecureSettings.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
/* notifyForDescendants */ false, mMenuTargetFeaturesContentObserver,
UserHandle.USER_CURRENT);
if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) {
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
mSecureSettings.getUriFor(ENABLED_ACCESSIBILITY_SERVICES),
/* notifyForDescendants */ false,
mMenuTargetFeaturesContentObserver,
UserHandle.USER_CURRENT);
}
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
mSecureSettings.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE),
/* notifyForDescendants */ false, mMenuSizeContentObserver,
UserHandle.USER_CURRENT);
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
mSecureSettings.getUriFor(ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED),
/* notifyForDescendants */ false, mMenuFadeOutContentObserver,
UserHandle.USER_CURRENT);
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
mSecureSettings.getUriFor(ACCESSIBILITY_FLOATING_MENU_OPACITY),
/* notifyForDescendants */ false, mMenuFadeOutContentObserver,
UserHandle.USER_CURRENT);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt
index 91bc0c1..eaf541d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt
@@ -29,12 +29,12 @@
import android.widget.TextView
import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
-import com.android.systemui.res.R
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener.ControlUnitType
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -46,7 +46,9 @@
import kotlin.math.roundToInt
/** The Dialog that contains a seekbar for changing the font size. */
-class FontScalingDialogDelegate @Inject constructor(
+class FontScalingDialogDelegate
+@Inject
+constructor(
private val context: Context,
private val systemUIDialogFactory: SystemUIDialog.Factory,
private val layoutInflater: LayoutInflater,
@@ -84,9 +86,9 @@
dialog.setTitle(R.string.font_scaling_dialog_title)
dialog.setView(layoutInflater.inflate(R.layout.font_scaling_dialog, null))
dialog.setPositiveButton(
- R.string.quick_settings_done,
- /* onClick = */ null,
- /* dismissOnClick = */ true
+ R.string.quick_settings_done,
+ /* onClick = */ null,
+ /* dismissOnClick = */ true
)
}
@@ -142,7 +144,7 @@
}
)
doneButton.setOnClickListener { dialog.dismiss() }
- systemSettings.registerContentObserver(Settings.System.FONT_SCALE, fontSizeObserver)
+ systemSettings.registerContentObserverSync(Settings.System.FONT_SCALE, fontSizeObserver)
}
/**
@@ -165,7 +167,7 @@
override fun onStop(dialog: SystemUIDialog) {
cancelUpdateFontScaleRunnable?.run()
cancelUpdateFontScaleRunnable = null
- systemSettings.unregisterContentObserver(fontSizeObserver)
+ systemSettings.unregisterContentObserverSync(fontSizeObserver)
}
@MainThread
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt
index 68c4a10..2970890 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt
@@ -75,7 +75,7 @@
) {
fun fetch(): Boolean = getIntForUser(key, if (defaultValue) 1 else 0, userId) > 0
- registerContentObserverForUser(
+ registerContentObserverForUserSync(
key,
false /* notifyForDescendants */,
object : ContentObserver(handler) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
index 153b7aa..8993a3b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
@@ -19,6 +19,7 @@
import android.annotation.SuppressLint
import android.app.DreamManager
import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming
import com.android.systemui.Flags.communalHub
import com.android.systemui.Flags.restartDreamOnUnocclude
import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -30,11 +31,11 @@
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample
import com.android.systemui.util.kotlin.sample
-import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import javax.inject.Inject
/**
* A [CoreStartable] responsible for automatically starting the dream when the communal hub is
@@ -78,6 +79,7 @@
if (
finishedState == KeyguardState.GLANCEABLE_HUB &&
!dreaming &&
+ !glanceableHubAllowKeyguardWhenDreaming() &&
dreamManager.canStartDreaming(isAwake)
) {
dreamManager.startDream()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
index 88cb64c..1c47e50 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -30,6 +30,7 @@
import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_FLAG
import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_INVALID_USER
import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_USER_SETTING
+import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.flags.FeatureFlagsClassic
@@ -59,6 +60,9 @@
/** Keyguard widgets enabled state by Device Policy Manager for the specified user. */
fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean>
+
+ /** The type of background to use for the hub. Used to experiment with different backgrounds. */
+ fun getBackground(user: UserInfo): Flow<CommunalBackgroundType>
}
@SysUISingleton
@@ -126,6 +130,21 @@
.emitOnStart()
.map { devicePolicyManager.areKeyguardWidgetsAllowed(user.id) }
+ override fun getBackground(user: UserInfo): Flow<CommunalBackgroundType> =
+ secureSettings
+ .observerFlow(userId = user.id, names = arrayOf(GLANCEABLE_HUB_BACKGROUND_SETTING))
+ .emitOnStart()
+ .map {
+ val intType =
+ secureSettings.getIntForUser(
+ GLANCEABLE_HUB_BACKGROUND_SETTING,
+ CommunalBackgroundType.DEFAULT.value,
+ user.id
+ )
+ CommunalBackgroundType.entries.find { type -> type.value == intType }
+ ?: CommunalBackgroundType.DEFAULT
+ }
+
private fun getEnabledByUser(user: UserInfo): Flow<Boolean> =
secureSettings
.observerFlow(userId = user.id, names = arrayOf(Settings.Secure.GLANCEABLE_HUB_ENABLED))
@@ -141,6 +160,7 @@
companion object {
const val GLANCEABLE_HUB_CONTENT_SETTING = "glanceable_hub_content_setting"
+ const val GLANCEABLE_HUB_BACKGROUND_SETTING = "glanceable_hub_background"
private const val ENABLED_SETTING_DEFAULT = 1
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
index 3e5126a..f043d58 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
@@ -21,6 +21,7 @@
import com.android.systemui.communal.data.model.CommunalEnabledState
import com.android.systemui.communal.data.model.CommunalWidgetCategories
import com.android.systemui.communal.data.repository.CommunalSettingsRepository
+import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.dagger.CommunalTableLog
@@ -30,6 +31,7 @@
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
@@ -38,6 +40,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -47,6 +50,7 @@
@Inject
constructor(
@Background private val bgScope: CoroutineScope,
+ @Background private val bgDispatcher: CoroutineDispatcher,
@Background private val bgExecutor: Executor,
private val repository: CommunalSettingsRepository,
userInteractor: SelectedUserInteractor,
@@ -78,6 +82,12 @@
initialValue = CommunalWidgetCategories.defaultCategories
)
+ /** The type of background to use for the hub. Used to experiment with different backgrounds */
+ val communalBackground: Flow<CommunalBackgroundType> =
+ userInteractor.selectedUserInfo
+ .flatMapLatest { user -> repository.getBackground(user) }
+ .flowOn(bgDispatcher)
+
private val workProfileUserInfoCallbackFlow: Flow<UserInfo?> = conflatedCallbackFlow {
fun send(profiles: List<UserInfo>) {
trySend(profiles.find { it.isManagedProfile })
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalBackgroundType.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalBackgroundType.kt
new file mode 100644
index 0000000..8b816db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalBackgroundType.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.communal.shared.model
+
+/** Models the types of background that can be shown on the hub. */
+enum class CommunalBackgroundType(val value: Int) {
+ DEFAULT(0),
+ STATIC_GRADIENT(1),
+ ANIMATED(2),
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 3d9e861..8cd5603 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -91,6 +91,12 @@
/** A list of all the communal content to be displayed in the communal hub. */
abstract val communalContent: Flow<List<CommunalContentModel>>
+ /**
+ * Whether to freeze the emission of the communalContent flow to prevent recomposition. Defaults
+ * to false, indicating that the flow will emit new update.
+ */
+ open val isCommunalContentFlowFrozen: Flow<Boolean> = flowOf(false)
+
/** Whether in edit mode for the communal hub. */
open val isEditMode = false
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 7f3a2dc..3e00b04 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -21,8 +21,10 @@
import android.view.accessibility.AccessibilityNodeInfo
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
@@ -38,7 +40,9 @@
import com.android.systemui.media.dagger.MediaModule
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.CoroutineScope
@@ -68,6 +72,7 @@
keyguardInteractor: KeyguardInteractor,
communalSceneInteractor: CommunalSceneInteractor,
private val communalInteractor: CommunalInteractor,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
tutorialInteractor: CommunalTutorialInteractor,
private val shadeInteractor: ShadeInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
@@ -76,8 +81,11 @@
private val logger = Logger(logBuffer, "CommunalViewModel")
+ /** Communal content saved from the previous emission when the flow is active (not "frozen"). */
+ private var frozenCommunalContent: List<CommunalContentModel>? = null
+
@OptIn(ExperimentalCoroutinesApi::class)
- override val communalContent: Flow<List<CommunalContentModel>> =
+ private val latestCommunalContent: Flow<List<CommunalContentModel>> =
tutorialInteractor.isTutorialAvailable
.flatMapLatest { isTutorialMode ->
if (isTutorialMode) {
@@ -93,9 +101,40 @@
}
}
.onEach { models ->
+ frozenCommunalContent = models
logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
}
+ /**
+ * Freeze the content flow, when an activity is about to show, like starting a timer via voice:
+ * 1) in handheld mode, use the keyguard occluded state;
+ * 2) in dreaming mode, where keyguard is already occluded by dream, use the dream wakeup
+ * signal. Since in this case the shell transition info does not include
+ * KEYGUARD_VISIBILITY_TRANSIT_FLAGS, KeyguardTransitionHandler will not run the
+ * occludeAnimation on KeyguardViewMediator.
+ */
+ override val isCommunalContentFlowFrozen: Flow<Boolean> =
+ allOf(
+ keyguardTransitionInteractor.isFinishedInState(KeyguardState.GLANCEABLE_HUB),
+ keyguardInteractor.isKeyguardOccluded,
+ not(keyguardInteractor.isAbleToDream)
+ )
+ .distinctUntilChanged()
+ .onEach { logger.d("isCommunalContentFlowFrozen: $it") }
+
+ override val communalContent: Flow<List<CommunalContentModel>> =
+ isCommunalContentFlowFrozen
+ .flatMapLatestConflated { isFrozen ->
+ if (isFrozen) {
+ flowOf(frozenCommunalContent ?: emptyList())
+ } else {
+ latestCommunalContent
+ }
+ }
+ .onEach { models ->
+ logger.d({ "CommunalContent: $str1" }) { str1 = models.joinToString { it.key } }
+ }
+
override val isEmptyState: Flow<Boolean> =
communalInteractor.widgetContent
.map { it.isEmpty() }
@@ -248,6 +287,10 @@
*/
val showGestureIndicator: Flow<Boolean> = not(keyguardInteractor.isDreaming)
+ /** The type of background to use for the hub. */
+ val communalBackground: Flow<CommunalBackgroundType> =
+ communalSettingsInteractor.communalBackground
+
companion object {
const val POPUP_AUTO_HIDE_TIMEOUT_MS = 12000L
}
@@ -255,5 +298,6 @@
sealed class PopupType {
object CtaTile : PopupType()
+
object CustomizeWidgetButton : PopupType()
}
diff --git a/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java
index 0bdc7f1..84807fb 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java
@@ -69,15 +69,15 @@
}
};
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED,
settingsObserver,
UserHandle.myUserId());
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED,
settingsObserver,
UserHandle.myUserId());
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
settingsObserver,
UserHandle.myUserId());
diff --git a/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java b/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java
index 3cf22b1..a679bfb 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java
@@ -67,7 +67,7 @@
@Override
public int getRequiredTypeAvailability() {
// TODO(b/339667383): create a new complication type if we decide to productionize this
- return COMPLICATION_TYPE_HOME_CONTROLS;
+ return COMPLICATION_TYPE_NONE;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt b/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt
index 1bbdfcd..4dbb32d 100644
--- a/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt
+++ b/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt
@@ -19,18 +19,16 @@
import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
/**
- * Retrieves whether or not the device is docked according to DockManager. Emits a starting value of
- * isDocked.
+ * Retrieves whether or not the device is docked according to DockManager. Emits a starting value
+ * of isDocked.
*/
fun DockManager.retrieveIsDocked(): Flow<Boolean> =
ConflatedCallbackFlow.conflatedCallbackFlow {
- val callback = DockManager.DockEventListener { trySend(isDocked) }
- addListener(callback)
- trySend(isDocked)
+ val callback = DockManager.DockEventListener { trySend(isDocked) }
+ addListener(callback)
+ trySend(isDocked)
- awaitClose { removeListener(callback) }
- }
- .distinctUntilChanged()
+ awaitClose { removeListener(callback) }
+ }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 3194942..7ae8409 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -426,7 +426,7 @@
}
if (!anyListening) {
- mSecureSettings.unregisterContentObserver(mSettingsObserver);
+ mSecureSettings.unregisterContentObserverSync(mSettingsObserver);
} else if (!mSettingRegistered) {
for (TriggerSensor s : mTriggerSensors) {
s.registerSettingsObserver(mSettingsObserver);
@@ -750,7 +750,7 @@
public void registerSettingsObserver(ContentObserver settingsObserver) {
if (mConfigured && !TextUtils.isEmpty(mSetting)) {
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
mSetting, mSettingsObserver, UserHandle.USER_ALL);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 96e708f..c6c57479 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -18,6 +18,7 @@
import static android.service.dreams.Flags.dreamWakeRedirect;
+import static com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming;
import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_WINDOW_TITLE;
import static com.android.systemui.dreams.dagger.DreamModule.DREAM_TOUCH_INSET_MANAGER;
import static com.android.systemui.dreams.dagger.DreamModule.HOME_CONTROL_PANEL_DREAM_COMPONENT;
@@ -395,7 +396,7 @@
return;
}
- redirectWake(mCommunalAvailable);
+ redirectWake(mCommunalAvailable && !glanceableHubAllowKeyguardWhenDreaming());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
index c5b3c53..4b07f78 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.dreams.ui.viewmodel
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -60,7 +61,7 @@
val showGlanceableHub =
communalInteractor.isCommunalEnabled.value &&
!keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
- if (showGlanceableHub) {
+ if (showGlanceableHub && !glanceableHubAllowKeyguardWhenDreaming()) {
communalInteractor.changeScene(CommunalScenes.Communal)
} else {
toLockscreenTransitionViewModel.startTransition()
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index f4f8796..3d3584e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -60,14 +60,6 @@
"notification_drag_to_contents"
)
- /**
- * This flag controls whether we register a listener for StatsD notification memory reports.
- * For statsd to actually call the listener however, a server-side toggle needs to be
- * enabled as well.
- */
- val NOTIFICATION_MEMORY_LOGGING_ENABLED =
- releasedFlag("notification_memory_logging_enabled")
-
// TODO(b/280783617): Tracking Bug
@Keep
@JvmField
@@ -386,9 +378,6 @@
val WARN_ON_BLOCKING_BINDER_TRANSACTIONS =
unreleasedFlag("warn_on_blocking_binder_transactions")
- // TODO:(b/283203305): Tracking bug
- @JvmField val TRIM_FONT_CACHES_AT_UNLOCK = unreleasedFlag("trim_font_caches_on_unlock")
-
// TODO(b/298380520): Tracking Bug
@JvmField
val USER_TRACKER_BACKGROUND_CALLBACKS = unreleasedFlag("user_tracker_background_callbacks")
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 49be03c..1e4fb4f 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -428,7 +428,7 @@
// get notified of phone state changes
mTelephonyListenerManager.addServiceStateListener(mPhoneStateListener);
- mGlobalSettings.registerContentObserver(
+ mGlobalSettings.registerContentObserverSync(
Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
mAirplaneModeObserver);
mHasVibrator = vibrator.hasVibrator();
@@ -453,7 +453,7 @@
public void destroy() {
mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
mTelephonyListenerManager.removeServiceStateListener(mPhoneStateListener);
- mGlobalSettings.unregisterContentObserver(mAirplaneModeObserver);
+ mGlobalSettings.unregisterContentObserverSync(mAirplaneModeObserver);
mConfigurationController.removeCallback(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index ea8d7d7..30b9583 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -19,7 +19,9 @@
import android.os.VibrationEffect
import android.view.View
import androidx.annotation.VisibleForTesting
+import com.android.systemui.animation.Expandable
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -51,6 +53,10 @@
var state = State.IDLE
private set
+ /** The QSTile and Expandable used to perform a long-click and click actions */
+ var qsTile: QSTile? = null
+ var expandable: Expandable? = null
+
/** Flow for view control and action */
private val _postedActionType = MutableStateFlow<ActionType?>(null)
val actionType: Flow<ActionType?> =
@@ -105,6 +111,7 @@
when (state) {
State.IDLE -> {
setState(State.TIMEOUT_WAIT)
+ _postedActionType.value = ActionType.WAIT_TAP_TIMEOUT
}
State.RUNNING_BACKWARDS -> _postedActionType.value = ActionType.CANCEL_ANIMATOR
else -> {}
@@ -112,16 +119,9 @@
}
fun handleActionUp() {
- when (state) {
- State.TIMEOUT_WAIT -> {
- _postedActionType.value = ActionType.CLICK
- setState(State.IDLE)
- }
- State.RUNNING_FORWARD -> {
- _postedActionType.value = ActionType.REVERSE_ANIMATOR
- setState(State.RUNNING_BACKWARDS)
- }
- else -> {}
+ if (state == State.RUNNING_FORWARD) {
+ _postedActionType.value = ActionType.REVERSE_ANIMATOR
+ setState(State.RUNNING_BACKWARDS)
}
}
@@ -129,6 +129,7 @@
when (state) {
State.TIMEOUT_WAIT -> {
setState(State.IDLE)
+ clearActionType()
}
State.RUNNING_FORWARD -> {
_postedActionType.value = ActionType.REVERSE_ANIMATOR
@@ -145,18 +146,23 @@
/** This function is called both when an animator completes or gets cancelled */
fun handleAnimationComplete() {
- if (state == State.RUNNING_FORWARD) {
- vibrate(snapEffect)
- _postedActionType.value = ActionType.LONG_PRESS
- }
- if (state != State.TIMEOUT_WAIT) {
- // This will happen if the animator did not finish by being cancelled
- setState(State.IDLE)
+ when (state) {
+ State.RUNNING_FORWARD -> {
+ setState(State.IDLE)
+ vibrate(snapEffect)
+ _postedActionType.value = ActionType.LONG_PRESS
+ }
+ State.RUNNING_BACKWARDS -> {
+ setState(State.IDLE)
+ clearActionType()
+ }
+ else -> {}
}
}
fun handleAnimationCancel() {
setState(State.TIMEOUT_WAIT)
+ _postedActionType.value = ActionType.WAIT_TAP_TIMEOUT
}
fun handleTimeoutComplete() {
@@ -190,9 +196,22 @@
effectDuration
)
setState(State.IDLE)
+ clearActionType()
return true
}
+ fun onTileClick(): Boolean {
+ if (state == State.TIMEOUT_WAIT) {
+ setState(State.IDLE)
+ clearActionType()
+ qsTile?.let {
+ it.click(expandable)
+ return true
+ }
+ }
+ return false
+ }
+
enum class State {
IDLE, /* The effect is idle waiting for touch input */
TIMEOUT_WAIT, /* The effect is waiting for a [PRESSED_TIMEOUT] period */
@@ -202,7 +221,7 @@
/* A type of action to perform on the view depending on the effect's state and logic */
enum class ActionType {
- CLICK,
+ WAIT_TAP_TIMEOUT,
LONG_PRESS,
RESET_AND_LONG_PRESS,
START_ANIMATOR,
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
index 4875f48..92a55ef 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
@@ -17,8 +17,6 @@
package com.android.systemui.haptics.qs
import android.animation.ValueAnimator
-import android.annotation.SuppressLint
-import android.view.MotionEvent
import android.view.ViewConfiguration
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.core.animation.doOnCancel
@@ -30,6 +28,7 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.qs.tileimpl.QSTileViewImpl
import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.filterNotNull
object QSLongPressEffectViewBinder {
@@ -41,9 +40,6 @@
): DisposableHandle? {
if (qsLongPressEffect == null) return null
- // Set the touch listener as the long-press effect
- setTouchListener(tile, qsLongPressEffect)
-
return tile.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
// Action to perform
@@ -52,18 +48,18 @@
qsLongPressEffect.actionType.filterNotNull().collect { action ->
when (action) {
- QSLongPressEffect.ActionType.CLICK -> {
- tile.performClick()
- qsLongPressEffect.clearActionType()
+ QSLongPressEffect.ActionType.WAIT_TAP_TIMEOUT -> {
+ delay(ViewConfiguration.getTapTimeout().toLong())
+ qsLongPressEffect.handleTimeoutComplete()
}
QSLongPressEffect.ActionType.LONG_PRESS -> {
tile.prepareForLaunch()
- tile.performLongClick()
+ qsLongPressEffect.qsTile?.longClick(qsLongPressEffect.expandable)
qsLongPressEffect.clearActionType()
}
QSLongPressEffect.ActionType.RESET_AND_LONG_PRESS -> {
tile.resetLongPressEffectProperties()
- tile.performLongClick()
+ qsLongPressEffect.qsTile?.longClick(qsLongPressEffect.expandable)
qsLongPressEffect.clearActionType()
}
QSLongPressEffect.ActionType.START_ANIMATOR -> {
@@ -106,22 +102,4 @@
}
}
}
-
- @SuppressLint("ClickableViewAccessibility")
- private fun setTouchListener(tile: QSTileViewImpl, longPressEffect: QSLongPressEffect?) {
- tile.setOnTouchListener { _, event ->
- when (event.actionMasked) {
- MotionEvent.ACTION_DOWN -> {
- tile.postDelayed(
- { longPressEffect?.handleTimeoutComplete() },
- ViewConfiguration.getTapTimeout().toLong(),
- )
- longPressEffect?.handleActionDown()
- }
- MotionEvent.ACTION_UP -> longPressEffect?.handleActionUp()
- MotionEvent.ACTION_CANCEL -> longPressEffect?.handleActionCancel()
- }
- true
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
index f16a3bd..90867edd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
@@ -19,10 +19,12 @@
import android.hardware.input.InputSettings
import com.android.systemui.CoreStartable
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.flags.Flags as LegacyFlag
import com.android.systemui.keyboard.backlight.ui.KeyboardBacklightDialogCoordinator
+import com.android.systemui.keyboard.docking.binder.KeyboardDockingIndicationViewBinder
import com.android.systemui.keyboard.stickykeys.ui.StickyKeysIndicatorCoordinator
import dagger.Lazy
import javax.inject.Inject
@@ -34,14 +36,18 @@
constructor(
private val keyboardBacklightDialogCoordinator: Lazy<KeyboardBacklightDialogCoordinator>,
private val stickyKeysIndicatorCoordinator: Lazy<StickyKeysIndicatorCoordinator>,
+ private val keyboardDockingIndicationViewBinder: Lazy<KeyboardDockingIndicationViewBinder>,
private val featureFlags: FeatureFlags,
) : CoreStartable {
override fun start() {
- if (featureFlags.isEnabled(Flags.KEYBOARD_BACKLIGHT_INDICATOR)) {
+ if (featureFlags.isEnabled(LegacyFlag.KEYBOARD_BACKLIGHT_INDICATOR)) {
keyboardBacklightDialogCoordinator.get().startListening()
}
if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
stickyKeysIndicatorCoordinator.get().startListening()
}
+ if (Flags.keyboardDockingIndicator()) {
+ keyboardDockingIndicationViewBinder.get().startListening()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt
new file mode 100644
index 0000000..f649be2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.keyboard.docking.binder
+
+import android.content.Context
+import android.graphics.Paint
+import android.graphics.PixelFormat
+import android.view.WindowManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyboard.docking.ui.KeyboardDockingIndicationView
+import com.android.systemui.keyboard.docking.ui.viewmodel.KeyboardDockingIndicationViewModel
+import com.android.systemui.surfaceeffects.PaintDrawCallback
+import com.android.systemui.surfaceeffects.glowboxeffect.GlowBoxEffect
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class KeyboardDockingIndicationViewBinder
+@Inject
+constructor(
+ context: Context,
+ @Application private val applicationScope: CoroutineScope,
+ private val viewModel: KeyboardDockingIndicationViewModel,
+ private val windowManager: WindowManager
+) {
+
+ private val windowLayoutParams =
+ WindowManager.LayoutParams().apply {
+ width = WindowManager.LayoutParams.MATCH_PARENT
+ height = WindowManager.LayoutParams.MATCH_PARENT
+ format = PixelFormat.TRANSLUCENT
+ type = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
+ fitInsetsTypes = 0 // Ignore insets from all system bars
+ title = "Edge glow effect"
+ flags =
+ (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
+ setTrustedOverlay()
+ }
+
+ private var glowEffect: GlowBoxEffect? = null
+ private val glowEffectView = KeyboardDockingIndicationView(context, null)
+
+ private val drawCallback =
+ object : PaintDrawCallback {
+ override fun onDraw(paint: Paint) {
+ glowEffectView.draw(paint)
+ }
+ }
+
+ private val stateChangedCallback =
+ object : GlowBoxEffect.AnimationStateChangedCallback {
+ override fun onStart() {
+ windowManager.addView(glowEffectView, windowLayoutParams)
+ }
+
+ override fun onEnd() {
+ windowManager.removeView(glowEffectView)
+ }
+ }
+
+ fun startListening() {
+ applicationScope.launch {
+ viewModel.edgeGlow.collect { config ->
+ if (glowEffect == null) {
+ glowEffect = GlowBoxEffect(config, drawCallback, stateChangedCallback)
+ } else {
+ glowEffect?.finish(force = true)
+ glowEffect!!.updateConfig(config)
+ }
+ }
+ }
+
+ applicationScope.launch {
+ viewModel.keyboardConnected.collect { connected ->
+ if (connected) {
+ glowEffect?.play()
+ } else {
+ glowEffect?.finish()
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/docking/domain/interactor/KeyboardDockingIndicationInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/docking/domain/interactor/KeyboardDockingIndicationInteractor.kt
new file mode 100644
index 0000000..c670b5e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/docking/domain/interactor/KeyboardDockingIndicationInteractor.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.keyboard.docking.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyboard.data.repository.KeyboardRepository
+import javax.inject.Inject
+
+/** Listens for keyboard docking event. */
+@SysUISingleton
+class KeyboardDockingIndicationInteractor
+@Inject
+constructor(keyboardRepository: KeyboardRepository) {
+ val onKeyboardConnected = keyboardRepository.isAnyKeyboardConnected
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/KeyboardDockingIndicationView.kt b/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/KeyboardDockingIndicationView.kt
new file mode 100644
index 0000000..de8b2cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/KeyboardDockingIndicationView.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.keyboard.docking.ui
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.util.AttributeSet
+import android.view.View
+
+/** View that's used for rendering keyboard docking indicator. */
+class KeyboardDockingIndicationView(context: Context?, attrs: AttributeSet?) :
+ View(context, attrs) {
+
+ private var paint: Paint? = null
+
+ override fun onDraw(canvas: Canvas) {
+ if (!canvas.isHardwareAccelerated) {
+ return
+ }
+ paint?.let { canvas.drawPaint(it) }
+ }
+
+ fun draw(paint: Paint) {
+ this.paint = paint
+ invalidate()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt
new file mode 100644
index 0000000..2578b78
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.keyboard.docking.ui.viewmodel
+
+import android.content.Context
+import android.view.Surface
+import android.view.WindowManager
+import com.android.settingslib.Utils
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyboard.docking.domain.interactor.KeyboardDockingIndicationInteractor
+import com.android.systemui.surfaceeffects.glowboxeffect.GlowBoxConfig
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class KeyboardDockingIndicationViewModel
+@Inject
+constructor(
+ private val windowManager: WindowManager,
+ private val context: Context,
+ keyboardDockingIndicationInteractor: KeyboardDockingIndicationInteractor,
+ configurationInteractor: ConfigurationInteractor,
+ @Background private val backgroundScope: CoroutineScope,
+) {
+
+ private val _edgeGlow: MutableStateFlow<GlowBoxConfig> = MutableStateFlow(createEffectConfig())
+ val edgeGlow = _edgeGlow.asStateFlow()
+ val keyboardConnected = keyboardDockingIndicationInteractor.onKeyboardConnected
+
+ init {
+ /**
+ * Expected behaviors:
+ * 1) On keyboard docking event, we play the animation for a fixed duration.
+ * 2) If the keyboard gets disconnected during the animation, we finish the animation with
+ * ease out.
+ * 3) If the configuration changes (e.g., device rotation), we force cancel the animation
+ * with no ease out.
+ */
+ backgroundScope.launch {
+ configurationInteractor.onAnyConfigurationChange.collect {
+ _edgeGlow.value = createEffectConfig()
+ }
+ }
+ }
+
+ private fun createEffectConfig(): GlowBoxConfig {
+ val bounds = windowManager.currentWindowMetrics.bounds
+ val width = bounds.width().toFloat()
+ val height = bounds.height().toFloat()
+
+ val startCenterX: Float
+ val startCenterY: Float
+ val endCenterX: Float
+ val endCenterY: Float
+ val boxWidth: Float
+ val boxHeight: Float
+
+ when (context.display.rotation) {
+ Surface.ROTATION_0 -> {
+ endCenterX = width
+ endCenterY = height * 0.5f
+ startCenterX = endCenterX + OFFSET
+ startCenterY = endCenterY
+ boxWidth = THICKNESS
+ boxHeight = height
+ }
+ Surface.ROTATION_90 -> {
+ endCenterX = width * 0.5f
+ endCenterY = 0f
+ startCenterX = endCenterX
+ startCenterY = endCenterY - OFFSET
+ boxWidth = width
+ boxHeight = THICKNESS
+ }
+ Surface.ROTATION_180 -> {
+ endCenterX = 0f
+ endCenterY = height * 0.5f
+ startCenterX = endCenterX - OFFSET
+ startCenterY = endCenterY
+ boxWidth = THICKNESS
+ boxHeight = height
+ }
+ Surface.ROTATION_270 -> {
+ endCenterX = width * 0.5f
+ endCenterY = height
+ startCenterX = endCenterX
+ startCenterY = endCenterY + OFFSET
+ boxWidth = width
+ boxHeight = THICKNESS
+ }
+ else -> { // Shouldn't happen. Just fall off to ROTATION_0
+ endCenterX = width
+ endCenterY = height * 0.5f
+ startCenterX = endCenterX + OFFSET
+ startCenterY = endCenterY
+ boxWidth = THICKNESS
+ boxHeight = height
+ }
+ }
+
+ return GlowBoxConfig(
+ startCenterX = startCenterX,
+ startCenterY = startCenterY,
+ endCenterX = endCenterX,
+ endCenterY = endCenterY,
+ width = boxWidth,
+ height = boxHeight,
+ color = Utils.getColorAttr(context, android.R.attr.colorAccent).defaultColor,
+ blurAmount = BLUR_AMOUNT,
+ duration = DURATION,
+ easeInDuration = EASE_DURATION,
+ easeOutDuration = EASE_DURATION
+ )
+ }
+
+ private companion object {
+ private const val OFFSET = 300f
+ private const val THICKNESS = 20f
+ private const val BLUR_AMOUNT = 700f
+ private const val DURATION = 3000L
+ private const val EASE_DURATION = 800L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
index 3cbcb2c..97ea16d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
@@ -18,21 +18,15 @@
import android.annotation.WorkerThread
import android.content.ComponentCallbacks2
-import android.graphics.HardwareRenderer
-import android.os.Trace
import android.util.Log
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
@@ -40,10 +34,7 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
/**
@@ -57,37 +48,15 @@
class ResourceTrimmer
@Inject
constructor(
- private val keyguardInteractor: KeyguardInteractor,
- private val powerInteractor: PowerInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val globalWindowManager: GlobalWindowManager,
@Application private val applicationScope: CoroutineScope,
@Background private val bgDispatcher: CoroutineDispatcher,
- private val featureFlags: FeatureFlags,
private val sceneInteractor: SceneInteractor,
-) : CoreStartable, WakefulnessLifecycle.Observer {
+) : CoreStartable {
override fun start() {
Log.d(LOG_TAG, "Resource trimmer registered.")
- if (com.android.systemui.Flags.trimResourcesWithBackgroundTrimAtLock()) {
- applicationScope.launch(bgDispatcher) {
- // We need to wait for the AoD transition (and animation) to complete.
- // This means we're waiting for isDreaming (== implies isDoze) and dozeAmount == 1f
- // signal. This is to make sure we don't clear font caches during animation which
- // would jank and leave stale data in memory.
- val isDozingFully =
- keyguardInteractor.dozeAmount.map { it == 1f }.distinctUntilChanged()
- combine(
- powerInteractor.isAsleep,
- keyguardInteractor.isDreaming,
- isDozingFully,
- ::Triple
- )
- .distinctUntilChanged()
- .collect { onWakefulnessUpdated(it.first, it.second, it.third) }
- }
- }
-
applicationScope.launch(bgDispatcher) {
// We drop 1 to avoid triggering on initial collect().
if (SceneContainerFlag.isEnabled) {
@@ -110,47 +79,9 @@
// lockscreen elements, especially clocks.
Log.d(LOG_TAG, "Sending TRIM_MEMORY_UI_HIDDEN.")
globalWindowManager.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
- if (featureFlags.isEnabled(Flags.TRIM_FONT_CACHES_AT_UNLOCK)) {
- if (DEBUG) {
- Log.d(LOG_TAG, "Trimming font caches since keyguard went away.")
- }
- globalWindowManager.trimCaches(HardwareRenderer.CACHE_TRIM_FONT)
- }
- }
-
- @WorkerThread
- private fun onWakefulnessUpdated(
- isAsleep: Boolean,
- isDreaming: Boolean,
- isDozingFully: Boolean
- ) {
- if (!com.android.systemui.Flags.trimResourcesWithBackgroundTrimAtLock()) {
- return
- }
-
- if (DEBUG) {
- Log.d(LOG_TAG, "isAsleep: $isAsleep Dreaming: $isDreaming DozeAmount: $isDozingFully")
- }
- // There are three scenarios:
- // * No dozing and no AoD at all - where we go directly to ASLEEP with isDreaming = false
- // and dozeAmount == 0f
- // * Dozing without Aod - where we go to ASLEEP with isDreaming = true and dozeAmount jumps
- // to 1f
- // * AoD - where we go to ASLEEP with iDreaming = true and dozeAmount slowly increases
- // to 1f
- val dozeDisabledAndScreenOff = isAsleep && !isDreaming
- val dozeEnabledAndDozeAnimationCompleted = isAsleep && isDreaming && isDozingFully
- if (dozeDisabledAndScreenOff || dozeEnabledAndDozeAnimationCompleted) {
- Trace.beginSection("ResourceTrimmer#trimMemory")
- Log.d(LOG_TAG, "SysUI asleep, trimming memory.")
- globalWindowManager.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
- globalWindowManager.trimCaches(HardwareRenderer.CACHE_TRIM_ALL)
- Trace.endSection()
- }
}
companion object {
private const val LOG_TAG = "ResourceTrimmer"
- private val DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index a2bbcad..f15896b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -183,7 +183,7 @@
val statusBarState: StateFlow<StatusBarState>
/** Observable for biometric unlock state which includes the mode and unlock source */
- val biometricUnlockState: Flow<BiometricUnlockModel>
+ val biometricUnlockState: StateFlow<BiometricUnlockModel>
fun setBiometricUnlockState(
unlockMode: BiometricUnlockMode,
@@ -307,17 +307,20 @@
private val _dismissAction: MutableStateFlow<DismissAction> =
MutableStateFlow(DismissAction.None)
override val dismissAction = _dismissAction.asStateFlow()
+
override fun setDismissAction(dismissAction: DismissAction) {
_dismissAction.value = dismissAction
}
private val _keyguardDone: MutableSharedFlow<KeyguardDone> = MutableSharedFlow()
override val keyguardDone = _keyguardDone.asSharedFlow()
+
override suspend fun setKeyguardDone(keyguardDoneType: KeyguardDone) {
_keyguardDone.emit(keyguardDoneType)
}
override val keyguardDoneAnimationsFinished: MutableSharedFlow<Unit> = MutableSharedFlow()
+
override fun keyguardDoneAnimationsFinished() {
keyguardDoneAnimationsFinished.tryEmit(Unit)
}
@@ -490,6 +493,7 @@
override fun onStartDream() {
trySendWithFailureLogging(true, TAG, "updated isDreamingWithOverlay")
}
+
override fun onWakeUp() {
trySendWithFailureLogging(false, TAG, "updated isDreamingWithOverlay")
}
@@ -618,7 +622,8 @@
private val _biometricUnlockState: MutableStateFlow<BiometricUnlockModel> =
MutableStateFlow(BiometricUnlockModel(BiometricUnlockMode.NONE, null))
- override val biometricUnlockState = _biometricUnlockState.asStateFlow()
+ override val biometricUnlockState: StateFlow<BiometricUnlockModel> =
+ _biometricUnlockState.asStateFlow()
override fun setBiometricUnlockState(
unlockMode: BiometricUnlockMode,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index f488d3b..8ec460a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -265,7 +265,7 @@
state: TransitionState
) {
if (updateTransitionId != transitionId) {
- Log.w(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
+ Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt
index 576fafd..ebc3483 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt
@@ -3,6 +3,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER
import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE
@@ -15,6 +16,7 @@
import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.StateFlow
@ExperimentalCoroutinesApi
@SysUISingleton
@@ -24,6 +26,8 @@
private val keyguardRepository: KeyguardRepository,
) {
+ val unlockState: StateFlow<BiometricUnlockModel> = keyguardRepository.biometricUnlockState
+
fun setBiometricUnlockState(
@WakeAndUnlockMode unlockStateInt: Int,
biometricUnlockSource: BiometricUnlockSource?,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index c44a40f..73835a3c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -37,6 +37,7 @@
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -47,8 +48,10 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.notification.NotificationUtils.interpolate
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
+import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import javax.inject.Provider
@@ -66,7 +69,9 @@
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
@@ -96,17 +101,54 @@
// TODO(b/296118689): move to a repository
private val _notificationPlaceholderBounds = MutableStateFlow(NotificationContainerBounds())
+ // When going to AOD, we interpolate bounds when receiving the new bounds
+ // When going back to LS, we'll apply new bounds directly
+ private val _nonSplitShadeNotifciationPlaceholderBounds =
+ _notificationPlaceholderBounds.pairwise().flatMapLatest { (oldBounds, newBounds) ->
+ val lastChangeStep = keyguardTransitionInteractor.transitionState.first()
+ if (lastChangeStep.to == AOD) {
+ keyguardTransitionInteractor.transitionState.map { step ->
+ val startingProgress = lastChangeStep.value
+ val progress = step.value
+ if (step.to == AOD && progress >= startingProgress) {
+ val adjustedProgress =
+ ((progress - startingProgress) / (1F - startingProgress)).coerceIn(
+ 0F,
+ 1F
+ )
+ val top = interpolate(oldBounds.top, newBounds.top, adjustedProgress)
+ val bottom =
+ interpolate(
+ oldBounds.bottom,
+ newBounds.bottom,
+ adjustedProgress.coerceIn(0F, 1F)
+ )
+ NotificationContainerBounds(top = top, bottom = bottom)
+ } else {
+ newBounds
+ }
+ }
+ } else {
+ flow { emit(newBounds) }
+ }
+ }
+
/** Bounds of the notification container. */
val notificationContainerBounds: StateFlow<NotificationContainerBounds> by lazy {
SceneContainerFlag.assertInLegacyMode()
combine(
_notificationPlaceholderBounds,
+ _nonSplitShadeNotifciationPlaceholderBounds,
sharedNotificationContainerInteractor.get().configurationBasedDimensions,
- ) { bounds, cfg ->
+ ) { bounds, nonSplitShadeBounds: NotificationContainerBounds, cfg ->
// We offset the placeholder bounds by the configured top margin to account for
// legacy placement behavior within notifications for splitshade.
- if (MigrateClocksToBlueprint.isEnabled && cfg.useSplitShade) {
- bounds.copy(bottom = bounds.bottom - cfg.keyguardSplitShadeTopMargin)
+ if (MigrateClocksToBlueprint.isEnabled) {
+ if (cfg.useSplitShade) {
+ bounds.copy(bottom = bounds.bottom - cfg.keyguardSplitShadeTopMargin)
+ } else {
+ nonSplitShadeBounds
+ }
} else bounds
}
.stateIn(
@@ -139,6 +181,8 @@
/** Doze transition information. */
val dozeTransitionModel: Flow<DozeTransitionModel> = repository.dozeTransitionModel
+ val isPulsing: Flow<Boolean> = dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING }
+
/**
* Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true,
* but not vice-versa.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 8ffa4bb..ccce3bf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -105,35 +105,33 @@
}
return combine(
- quickAffordanceAlwaysVisible(position),
- keyguardInteractor.isDozing,
- if (SceneContainerFlag.isEnabled) {
- sceneInteractor
- .get()
- .transitionState
- .map {
- when (it) {
- is ObservableTransitionState.Idle ->
- it.currentScene == Scenes.Lockscreen
- is ObservableTransitionState.Transition ->
- it.fromScene == Scenes.Lockscreen ||
- it.toScene == Scenes.Lockscreen
- }
+ quickAffordanceAlwaysVisible(position),
+ keyguardInteractor.isDozing,
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor
+ .get()
+ .transitionState
+ .map {
+ when (it) {
+ is ObservableTransitionState.Idle ->
+ it.currentScene == Scenes.Lockscreen
+ is ObservableTransitionState.Transition ->
+ it.fromScene == Scenes.Lockscreen || it.toScene == Scenes.Lockscreen
}
- .distinctUntilChanged()
- } else {
- keyguardInteractor.isKeyguardShowing
- },
- shadeInteractor.anyExpansion.map { it < 1.0f }.distinctUntilChanged(),
- biometricSettingsRepository.isCurrentUserInLockdown,
- ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown ->
- if (!isDozing && isKeyguardShowing && isQuickSettingsVisible && !isUserInLockdown) {
- affordance
- } else {
- KeyguardQuickAffordanceModel.Hidden
- }
+ }
+ .distinctUntilChanged()
+ } else {
+ keyguardInteractor.isKeyguardShowing
+ },
+ shadeInteractor.anyExpansion.map { it < 1.0f }.distinctUntilChanged(),
+ biometricSettingsRepository.isCurrentUserInLockdown,
+ ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown ->
+ if (!isDozing && isKeyguardShowing && isQuickSettingsVisible && !isUserInLockdown) {
+ affordance
+ } else {
+ KeyguardQuickAffordanceModel.Hidden
}
- .distinctUntilChanged()
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt
index 6d579f3..f5b82cf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt
@@ -40,6 +40,7 @@
rebuildSections: List<KeyguardSection> = listOf(),
bindData: Boolean = true
) {
+ rebuildSections.forEach { it.onRebuildBegin() }
val prevSections = previousBlueprint?.sections ?: listOf()
val skipSections = sections.intersect(prevSections).subtract(rebuildSections)
prevSections.subtract(skipSections).forEach { it.removeViews(constraintLayout) }
@@ -49,6 +50,7 @@
it.bindData(constraintLayout)
}
}
+ rebuildSections.forEach { it.onRebuildEnd() }
}
/** Rebuilds views for the target sections, or all of them if unspecified. */
@@ -61,6 +63,7 @@
return
}
+ rebuildSections.forEach { it.onRebuildBegin() }
rebuildSections.forEach { it.removeViews(constraintLayout) }
rebuildSections.forEach {
it.addViews(constraintLayout)
@@ -68,6 +71,7 @@
it.bindData(constraintLayout)
}
}
+ rebuildSections.forEach { it.onRebuildEnd() }
}
fun applyConstraints(constraintSet: ConstraintSet) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt
index 48a2146..473b08f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt
@@ -33,6 +33,12 @@
/** Removes views and does any data binding destruction. */
abstract fun removeViews(constraintLayout: ConstraintLayout)
+ /* Notifies the section is being rebuilt */
+ open fun onRebuildBegin() {}
+
+ /* Notifies the secion that the rebuild is complete */
+ open fun onRebuildEnd() {}
+
/**
* Defines equality as same class.
*
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index 807c322..23c2491 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -65,7 +65,7 @@
val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
val disposableHandle =
view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
launch("$TAG#viewModel.alpha") {
// Do not independently apply alpha, as [KeyguardRootViewModel] should work
// for this and all its children
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index 1cf009d..b9a79dc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -30,7 +30,6 @@
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.tracing.coroutines.launch
import com.android.settingslib.Utils
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.view.LaunchableImageView
@@ -81,8 +80,8 @@
val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
val disposableHandle =
view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch("$TAG#viewModel.collect") {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
viewModel.collect { buttonModel ->
updateButton(
view = button,
@@ -94,7 +93,7 @@
}
}
- launch("$TAG#updateButtonAlpha") {
+ launch {
updateButtonAlpha(
view = button,
viewModel = viewModel,
@@ -102,7 +101,7 @@
)
}
- launch("$TAG#configurationBasedDimensions") {
+ launch {
configurationBasedDimensions.collect { dimensions ->
button.updateLayoutParams<ViewGroup.LayoutParams> {
width = dimensions.buttonSizePx.width
@@ -324,6 +323,4 @@
private data class ConfigurationBasedDimensions(
val buttonSizePx: Size,
)
-
- private const val TAG = "KeyguardQuickAffordanceViewBinder"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index 2d6690f..0435531 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -56,6 +56,14 @@
private var smartspaceVisibilityListener: OnGlobalLayoutListener? = null
private var pastVisibility: Int = -1
+ override fun onRebuildBegin() {
+ smartspaceController.suppressDisconnects = true
+ }
+
+ override fun onRebuildEnd() {
+ smartspaceController.suppressDisconnects = false
+ }
+
override fun addViews(constraintLayout: ConstraintLayout) {
if (!MigrateClocksToBlueprint.isEnabled) return
if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index 244d842..c4383fc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -18,7 +18,6 @@
package com.android.systemui.keyguard.ui.viewmodel
import androidx.annotation.VisibleForTesting
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -29,23 +28,19 @@
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.stateIn
@OptIn(ExperimentalCoroutinesApi::class)
class KeyguardQuickAffordancesCombinedViewModel
@Inject
constructor(
- @Application applicationScope: CoroutineScope,
private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor,
private val keyguardInteractor: KeyguardInteractor,
shadeInteractor: ShadeInteractor,
@@ -89,20 +84,15 @@
/** The only time the expansion is important is while lockscreen is actively displayed */
private val shadeExpansionAlpha =
combine(
- showingLockscreen,
- shadeInteractor.anyExpansion,
- ) { showingLockscreen, expansion ->
- if (showingLockscreen) {
- 1 - expansion
- } else {
- 0f
- }
+ showingLockscreen,
+ shadeInteractor.anyExpansion,
+ ) { showingLockscreen, expansion ->
+ if (showingLockscreen) {
+ 1 - expansion
+ } else {
+ 0f
}
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Lazily,
- initialValue = 0f,
- )
+ }
/**
* ID of the slot that's currently selected in the preview that renders exclusively in the
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 8316b3a..edead51 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -42,6 +42,7 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
@@ -123,6 +124,7 @@
class MediaCarouselController
@Inject
constructor(
+ @Application applicationScope: CoroutineScope,
private val context: Context,
private val mediaControlPanelFactory: Provider<MediaControlPanel>,
private val visualStabilityProvider: VisualStabilityProvider,
@@ -387,15 +389,15 @@
repeatOnLifecycle(Lifecycle.State.STARTED) {
listenForAnyStateToGoneKeyguardTransition(this)
listenForAnyStateToLockscreenTransition(this)
- listenForLockscreenSettingChanges(this)
if (!mediaFlags.isSceneContainerEnabled()) return@repeatOnLifecycle
listenForMediaItemsChanges(this)
}
}
+ listenForLockscreenSettingChanges(applicationScope)
// Notifies all active players about animation scale changes.
- globalSettings.registerContentObserver(
+ globalSettings.registerContentObserverSync(
Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
animationScaleObserver
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
index 6589038..601d563 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
@@ -167,6 +167,7 @@
private var targetBounds: Rect = Rect()
private val mediaFrame
get() = mediaCarouselController.mediaFrame
+
private var statusbarState: Int = statusBarStateController.state
private var animator =
ValueAnimator.ofFloat(0.0f, 1.0f).apply {
@@ -600,7 +601,7 @@
}
}
}
- secureSettings.registerContentObserverForUser(
+ secureSettings.registerContentObserverForUserSync(
Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
settingsObserver,
UserHandle.USER_ALL
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 2dc09e5..41cd2c4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -500,11 +500,13 @@
final boolean previousForcedVisible = mIsButtonForcedVisible;
mIsButtonForcedVisible =
mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
+ // Update this before calling mButtonForcedVisibleCallback since NavigationBar will relayout
+ // and query isHandlingGestures() as a part of the callback
+ mIsBackGestureAllowed = !mIsButtonForcedVisible;
if (previousForcedVisible != mIsButtonForcedVisible
&& mButtonForcedVisibleCallback != null) {
mButtonForcedVisibleCallback.accept(mIsButtonForcedVisible);
}
- mIsBackGestureAllowed = !mIsButtonForcedVisible;
final DisplayMetrics dm = res.getDisplayMetrics();
final float defaultGestureHeight = res.getDimension(
diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
index 2d460a0..765b45b 100644
--- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
@@ -341,7 +341,7 @@
}
mQRCodeScannerPreferenceObserver.forEach((key, value) -> {
- mSecureSettings.unregisterContentObserver(value);
+ mSecureSettings.unregisterContentObserverSync(value);
});
// Reset cached values to default as we are no longer listening
@@ -418,7 +418,7 @@
});
}
});
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
mSecureSettings.getUriFor(LOCK_SCREEN_SHOW_QR_CODE_SCANNER), false,
mQRCodeScannerPreferenceObserver.get(userId), userId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
index 2fafba1..e4bafcd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
@@ -52,7 +52,9 @@
*
* It also handles restore gracefully.
*/
-class AutoAddTracker @VisibleForTesting constructor(
+class AutoAddTracker
+@VisibleForTesting
+constructor(
private val secureSettings: SecureSettings,
private val broadcastDispatcher: BroadcastDispatcher,
private val qsHost: QSHost,
@@ -66,39 +68,43 @@
private val FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED)
}
- @GuardedBy("autoAdded")
- private val autoAdded = ArraySet<String>()
+ @GuardedBy("autoAdded") private val autoAdded = ArraySet<String>()
private var restoredTiles: Map<String, AutoTile>? = null
override val currentUserId: Int
get() = userId
- private val contentObserver = object : ContentObserver(mainHandler) {
- override fun onChange(
- selfChange: Boolean,
- uris: Collection<Uri>,
- flags: Int,
- _userId: Int
- ) {
- if (_userId != userId) {
- // Ignore changes outside of our user. We'll load the correct value on user change
- return
+ private val contentObserver =
+ object : ContentObserver(mainHandler) {
+ override fun onChange(
+ selfChange: Boolean,
+ uris: Collection<Uri>,
+ flags: Int,
+ _userId: Int
+ ) {
+ if (_userId != userId) {
+ // Ignore changes outside of our user. We'll load the correct value on user
+ // change
+ return
+ }
+ loadTiles()
}
- loadTiles()
}
- }
- private val restoreReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- if (intent.action != Intent.ACTION_SETTING_RESTORED) return
- processRestoreIntent(intent)
+ private val restoreReceiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action != Intent.ACTION_SETTING_RESTORED) return
+ processRestoreIntent(intent)
+ }
}
- }
private fun processRestoreIntent(intent: Intent) {
when (intent.getStringExtra(Intent.EXTRA_SETTING_NAME)) {
Settings.Secure.QS_TILES -> {
- restoredTiles = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
+ restoredTiles =
+ intent
+ .getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
?.split(DELIMITER)
?.mapIndexed(::AutoTile)
?.associateBy(AutoTile::tileType)
@@ -109,13 +115,11 @@
}
Settings.Secure.QS_AUTO_ADDED_TILES -> {
restoredTiles?.let { restoredTiles ->
- val restoredAutoAdded = intent
- .getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
- ?.split(DELIMITER)
+ val restoredAutoAdded =
+ intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)?.split(DELIMITER)
?: emptyList()
- val autoAddedBeforeRestore = intent
- .getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE)
- ?.split(DELIMITER)
+ val autoAddedBeforeRestore =
+ intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE)?.split(DELIMITER)
?: emptyList()
val tilesToRemove = restoredAutoAdded.filter { it !in restoredTiles }
@@ -123,50 +127,51 @@
Log.d(TAG, "Removing tiles: $tilesToRemove")
qsHost.removeTiles(tilesToRemove)
}
- val tiles = synchronized(autoAdded) {
- autoAdded.clear()
- autoAdded.addAll(restoredAutoAdded + autoAddedBeforeRestore)
- getTilesFromListLocked()
- }
+ val tiles =
+ synchronized(autoAdded) {
+ autoAdded.clear()
+ autoAdded.addAll(restoredAutoAdded + autoAddedBeforeRestore)
+ getTilesFromListLocked()
+ }
saveTiles(tiles)
- } ?: run {
- Log.w(TAG, "${Settings.Secure.QS_AUTO_ADDED_TILES} restored before " +
- "${Settings.Secure.QS_TILES} for user $userId")
}
+ ?: run {
+ Log.w(
+ TAG,
+ "${Settings.Secure.QS_AUTO_ADDED_TILES} restored before " +
+ "${Settings.Secure.QS_TILES} for user $userId"
+ )
+ }
}
else -> {} // Do nothing for other Settings
}
}
- /**
- * Init method must be called after construction to start listening
- */
+ /** Init method must be called after construction to start listening */
fun initialize() {
dumpManager.registerDumpable(TAG, this)
loadTiles()
- secureSettings.registerContentObserverForUser(
- secureSettings.getUriFor(Settings.Secure.QS_AUTO_ADDED_TILES),
- contentObserver,
- UserHandle.USER_ALL
+ secureSettings.registerContentObserverForUserSync(
+ secureSettings.getUriFor(Settings.Secure.QS_AUTO_ADDED_TILES),
+ contentObserver,
+ UserHandle.USER_ALL
)
registerBroadcastReceiver()
}
- /**
- * Unregister listeners, receivers and observers
- */
+ /** Unregister listeners, receivers and observers */
fun destroy() {
dumpManager.unregisterDumpable(TAG)
- secureSettings.unregisterContentObserver(contentObserver)
+ secureSettings.unregisterContentObserverSync(contentObserver)
unregisterBroadcastReceiver()
}
private fun registerBroadcastReceiver() {
broadcastDispatcher.registerReceiver(
- restoreReceiver,
- FILTER,
- backgroundExecutor,
- UserHandle.of(userId)
+ restoreReceiver,
+ FILTER,
+ backgroundExecutor,
+ UserHandle.of(userId)
)
}
@@ -186,13 +191,9 @@
fun getRestoredTilePosition(tile: String): Int =
restoredTiles?.get(tile)?.index ?: QSHost.POSITION_AT_END
- /**
- * Returns `true` if the tile has been auto-added before
- */
+ /** Returns `true` if the tile has been auto-added before */
fun isAdded(tile: String): Boolean {
- return synchronized(autoAdded) {
- tile in autoAdded
- }
+ return synchronized(autoAdded) { tile in autoAdded }
}
/**
@@ -201,13 +202,14 @@
* From here on, [isAdded] will return true for that tile.
*/
fun setTileAdded(tile: String) {
- val tiles = synchronized(autoAdded) {
- if (autoAdded.add(tile)) {
- getTilesFromListLocked()
- } else {
- null
+ val tiles =
+ synchronized(autoAdded) {
+ if (autoAdded.add(tile)) {
+ getTilesFromListLocked()
+ } else {
+ null
+ }
}
- }
tiles?.let { saveTiles(it) }
}
@@ -217,13 +219,14 @@
* This allows for this tile to be auto-added again in the future.
*/
fun setTileRemoved(tile: String) {
- val tiles = synchronized(autoAdded) {
- if (autoAdded.remove(tile)) {
- getTilesFromListLocked()
- } else {
- null
+ val tiles =
+ synchronized(autoAdded) {
+ if (autoAdded.remove(tile)) {
+ getTilesFromListLocked()
+ } else {
+ null
+ }
}
- }
tiles?.let { saveTiles(it) }
}
@@ -233,12 +236,12 @@
private fun saveTiles(tiles: String) {
secureSettings.putStringForUser(
- Settings.Secure.QS_AUTO_ADDED_TILES,
- tiles,
- /* tag */ null,
- /* makeDefault */ false,
- userId,
- /* overrideableByRestore */ true
+ Settings.Secure.QS_AUTO_ADDED_TILES,
+ tiles,
+ /* tag */ null,
+ /* makeDefault */ false,
+ userId,
+ /* overrideableByRestore */ true
)
}
@@ -261,7 +264,9 @@
}
@SysUISingleton
- class Builder @Inject constructor(
+ class Builder
+ @Inject
+ constructor(
private val secureSettings: SecureSettings,
private val broadcastDispatcher: BroadcastDispatcher,
private val qsHost: QSHost,
@@ -278,16 +283,16 @@
fun build(): AutoAddTracker {
return AutoAddTracker(
- secureSettings,
- broadcastDispatcher,
- qsHost,
- dumpManager,
- handler,
- executor,
- userId
+ secureSettings,
+ broadcastDispatcher,
+ qsHost,
+ dumpManager,
+ handler,
+ executor,
+ userId
)
}
}
private data class AutoTile(val index: Int, val tileType: String)
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 828d6ed..03c2aa6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -108,7 +108,7 @@
private AutoTileManager mAutoTiles;
private final ArrayList<QSFactory> mQsFactories = new ArrayList<>();
private int mCurrentUser;
- private final ShadeController mShadeController;
+ private final Lazy<ShadeController> mShadeControllerProvider;
private Context mUserContext;
private UserTracker mUserTracker;
private SecureSettings mSecureSettings;
@@ -130,7 +130,7 @@
PluginManager pluginManager,
TunerService tunerService,
Provider<AutoTileManager> autoTiles,
- ShadeController shadeController,
+ Lazy<ShadeController> shadeControllerProvider,
QSLogger qsLogger,
UserTracker userTracker,
SecureSettings secureSettings,
@@ -149,7 +149,7 @@
mUserFileManager = userFileManager;
mFeatureFlags = featureFlags;
- mShadeController = shadeController;
+ mShadeControllerProvider = shadeControllerProvider;
if (featureFlags.getTilesEnabled()) {
mQsFactories.add(newQsTileFactoryProvider.get());
@@ -216,17 +216,17 @@
@Override
public void collapsePanels() {
- mShadeController.postAnimateCollapseShade();
+ mShadeControllerProvider.get().postAnimateCollapseShade();
}
@Override
public void forceCollapsePanels() {
- mShadeController.postAnimateForceCollapseShade();
+ mShadeControllerProvider.get().postAnimateForceCollapseShade();
}
@Override
public void openPanels() {
- mShadeController.postAnimateExpandQs();
+ mShadeControllerProvider.get().postAnimateExpandQs();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
index 4fc6609..846d63f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
@@ -77,8 +77,8 @@
public void onUserChanged(int newUser, Context userContext) {
synchronized (mListeners) {
if (mListeners.size() > 0) {
- mSecureSettings.unregisterContentObserver(mContentObserver);
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.unregisterContentObserverSync(mContentObserver);
+ mSecureSettings.registerContentObserverForUserSync(
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
false, mContentObserver, newUser);
}
@@ -94,7 +94,7 @@
if (!mListeners.contains(listener)) {
mListeners.add(listener);
if (mListeners.size() == 1) {
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
false, mContentObserver, mUserTracker.getUserId());
}
@@ -106,7 +106,7 @@
public void removeCallback(@androidx.annotation.NonNull Listener listener) {
synchronized (mListeners) {
if (mListeners.remove(listener) && mListeners.size() == 0) {
- mSecureSettings.unregisterContentObserver(mContentObserver);
+ mSecureSettings.unregisterContentObserverSync(mContentObserver);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
index eb11568..6092348 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
@@ -74,10 +74,10 @@
mListening = listening;
if (listening) {
mObservedValue = getValueFromProvider();
- mSettingsProxy.registerContentObserver(
+ mSettingsProxy.registerContentObserverSync(
mSettingsProxy.getUriFor(mSettingName), false, this);
} else {
- mSettingsProxy.unregisterContentObserver(this);
+ mSettingsProxy.unregisterContentObserverSync(this);
mObservedValue = mDefaultValue;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java
index 539c2d6..1b34c33 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java
@@ -76,10 +76,10 @@
mListening = listening;
if (listening) {
mObservedValue = getValueFromProvider();
- mSettingsProxy.registerContentObserverForUser(
+ mSettingsProxy.registerContentObserverForUserSync(
mSettingsProxy.getUriFor(mSettingName), false, this, mUserId);
} else {
- mSettingsProxy.unregisterContentObserver(this);
+ mSettingsProxy.unregisterContentObserverSync(this);
mObservedValue = mDefaultValue;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepository.kt
index d452241..9fcb0db 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepository.kt
@@ -44,9 +44,8 @@
@Background private val bgDispatcher: CoroutineDispatcher,
) {
- private val changeEvents = MutableSharedFlow<ChangeAction>(
- extraBufferCapacity = CHANGES_BUFFER_SIZE
- )
+ private val changeEvents =
+ MutableSharedFlow<ChangeAction>(extraBufferCapacity = CHANGES_BUFFER_SIZE)
private lateinit var _autoAdded: StateFlow<Set<TileSpec>>
@@ -85,8 +84,8 @@
trySend(Unit)
}
}
- secureSettings.registerContentObserverForUser(SETTING, observer, userId)
- awaitClose { secureSettings.unregisterContentObserver(observer) }
+ secureSettings.registerContentObserverForUserSync(SETTING, observer, userId)
+ awaitClose { secureSettings.unregisterContentObserverSync(observer) }
}
.map { load() }
.flowOn(bgDispatcher)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
index aca8733..1f9570a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
@@ -98,8 +98,8 @@
trySend(Unit)
}
}
- secureSettings.registerContentObserverForUser(SETTING, observer, userId)
- awaitClose { secureSettings.unregisterContentObserver(observer) }
+ secureSettings.registerContentObserverForUserSync(SETTING, observer, userId)
+ awaitClose { secureSettings.unregisterContentObserverSync(observer) }
}
.map { loadTilesFromSettings(userId) }
.flowOn(backgroundDispatcher)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 1143c30..762dacd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -19,6 +19,7 @@
import android.animation.ArgbEvaluator
import android.animation.PropertyValuesHolder
import android.animation.ValueAnimator
+import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.Configuration
@@ -37,6 +38,7 @@
import android.util.TypedValue
import android.view.Gravity
import android.view.LayoutInflater
+import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
@@ -395,15 +397,22 @@
override fun init(tile: QSTile) {
val expandable = Expandable.fromView(this)
- init(
+ if (quickSettingsVisualHapticsLongpress()) {
+ isHapticFeedbackEnabled = false
+ longPressEffect?.qsTile = tile
+ longPressEffect?.expandable = expandable
+ init(
+ { _: View? -> longPressEffect?.onTileClick() },
+ null, // Haptics and long-clicks will be handled by the [QSLongPressEffect]
+ )
+ } else {
+ init(
{ _: View? -> tile.click(expandable) },
{ _: View? ->
tile.longClick(expandable)
true
- }
- )
- if (quickSettingsVisualHapticsLongpress()) {
- isHapticFeedbackEnabled = false // Haptics will be handled by the [QSLongPressEffect]
+ },
+ )
}
}
@@ -541,6 +550,20 @@
return sb.toString()
}
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouchEvent(event: MotionEvent?): Boolean {
+ // let the View run the onTouch logic for click and long-click detection
+ val result = super.onTouchEvent(event)
+ if (longPressEffect != null) {
+ when (event?.actionMasked) {
+ MotionEvent.ACTION_DOWN -> longPressEffect.handleActionDown()
+ MotionEvent.ACTION_UP -> longPressEffect.handleActionUp()
+ MotionEvent.ACTION_CANCEL -> longPressEffect.handleActionCancel()
+ }
+ }
+ return result
+ }
+
// HANDLE STATE CHANGES RELATED METHODS
protected open fun handleStateChanged(state: QSTile.State) {
@@ -675,7 +698,6 @@
// Long-press effects might have been enabled before but the new state does not
// handle a long-press. In this case, we go back to the behaviour of a regular tile
// and clean-up the resources
- setOnTouchListener(null)
unbindLongPressEffect()
showRippleEffect = isClickable
initialLongPressProperties = null
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
index d48d55d..c1a5646 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
@@ -21,8 +21,12 @@
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -37,7 +41,11 @@
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- overlayShadeViewModel: OverlayShadeViewModel,
+ val overlayShadeViewModel: OverlayShadeViewModel,
+ val brightnessSliderViewModel: BrightnessSliderViewModel,
+ val tileGridViewModel: TileGridViewModel,
+ val editModeViewModel: EditModeViewModel,
+ val qsSceneAdapter: QSSceneAdapter,
) {
val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
overlayShadeViewModel.backgroundScene
diff --git a/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt b/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt
index 3c0aa38..09fd7df 100644
--- a/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt
@@ -66,9 +66,9 @@
}
}
- globalSettings.registerContentObserver(RETAIL_MODE_SETTING, observer)
+ globalSettings.registerContentObserverSync(RETAIL_MODE_SETTING, observer)
- awaitClose { globalSettings.unregisterContentObserver(observer) }
+ awaitClose { globalSettings.unregisterContentObserverSync(observer) }
}
.onStart { emit(Unit) }
.map { globalSettings.getInt(RETAIL_MODE_SETTING, 0) != 0 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index 8169dec..7a9d09a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -20,6 +20,7 @@
import com.android.systemui.notifications.ui.composable.NotificationsShadeSessionModule
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.scene.domain.startable.SceneContainerStartable
+import com.android.systemui.scene.domain.startable.ScrimStartable
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.shared.flag.DualShade
@@ -50,6 +51,11 @@
@Binds
@IntoMap
+ @ClassKey(ScrimStartable::class)
+ fun scrimStartable(impl: ScrimStartable): CoreStartable
+
+ @Binds
+ @IntoMap
@ClassKey(WindowRootViewVisibilityInteractor::class)
fun bindWindowRootViewVisibilityInteractor(
impl: WindowRootViewVisibilityInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 9bd2694..7e6dfb8 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -21,6 +21,7 @@
import com.android.systemui.notifications.ui.composable.NotificationsShadeSessionModule
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.scene.domain.startable.SceneContainerStartable
+import com.android.systemui.scene.domain.startable.ScrimStartable
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.shared.flag.DualShade
@@ -56,6 +57,11 @@
@Binds
@IntoMap
+ @ClassKey(ScrimStartable::class)
+ fun scrimStartable(impl: ScrimStartable): CoreStartable
+
+ @Binds
+ @IntoMap
@ClassKey(WindowRootViewVisibilityInteractor::class)
fun bindWindowRootViewVisibilityInteractor(
impl: WindowRootViewVisibilityInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt
new file mode 100644
index 0000000..373916a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt
@@ -0,0 +1,217 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.domain.startable
+
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.CoreStartable
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.keyguard.domain.interactor.BiometricUnlockInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.phone.DozeServiceHost
+import com.android.systemui.statusbar.phone.ScrimController
+import com.android.systemui.statusbar.phone.ScrimState
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class ScrimStartable
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val scrimController: ScrimController,
+ private val sceneInteractor: SceneInteractor,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val occlusionInteractor: SceneContainerOcclusionInteractor,
+ private val biometricUnlockInteractor: BiometricUnlockInteractor,
+ private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ private val alternateBouncerInteractor: AlternateBouncerInteractor,
+ private val shadeInteractor: ShadeInteractor,
+ private val brightnessMirrorShowingInteractor: BrightnessMirrorShowingInteractor,
+ private val dozeServiceHost: DozeServiceHost,
+) : CoreStartable {
+
+ override fun start() {
+ if (!SceneContainerFlag.isEnabled) {
+ return
+ }
+
+ hydrateScrimState()
+ }
+
+ private fun hydrateScrimState() {
+ applicationScope.launch {
+ combine(
+ deviceEntryInteractor.isDeviceEntered,
+ occlusionInteractor.invisibleDueToOcclusion,
+ sceneInteractor.currentScene,
+ sceneInteractor.transitionState,
+ keyguardInteractor.isDozing,
+ keyguardInteractor.isDreaming,
+ biometricUnlockInteractor.unlockState,
+ shadeInteractor.isAnyExpanded,
+ brightnessMirrorShowingInteractor.isShowing,
+ keyguardInteractor.isPulsing,
+ ) { flowValues ->
+ val isDeviceEntered = flowValues[0] as Boolean
+ val isOccluded = flowValues[1] as Boolean
+ val currentScene = flowValues[2] as SceneKey
+ val transitionState = flowValues[3] as ObservableTransitionState
+ val isDozing = flowValues[4] as Boolean
+ val isDreaming = flowValues[5] as Boolean
+ val biometricUnlockState = flowValues[6] as BiometricUnlockModel
+ val isAnyShadeExpanded = flowValues[7] as Boolean
+ val isBrightnessMirrorVisible = flowValues[8] as Boolean
+ val isPulsing = flowValues[9] as Boolean
+
+ // This is true when the lockscreen scene is either the current scene or
+ // somewhere in the navigation back stack of scenes.
+ val isOnKeyguard = !isDeviceEntered
+ val isCurrentSceneBouncer = currentScene == Scenes.Bouncer
+ // This is true when moving away from one of the keyguard scenes to the gone
+ // scene. It happens only when unlocking or when dismissing a dismissible
+ // lockscreen.
+ val isTransitioningAwayFromKeyguard =
+ transitionState is ObservableTransitionState.Transition &&
+ transitionState.fromScene.isKeyguard() &&
+ transitionState.toScene == Scenes.Gone
+
+ // This is true when any of the shade scenes is the current scene.
+ val isCurrentSceneShade = currentScene.isShade()
+ // This is true when moving into one of the shade scenes when a non-shade scene.
+ val isTransitioningToShade =
+ transitionState is ObservableTransitionState.Transition &&
+ !transitionState.fromScene.isShade() &&
+ transitionState.toScene.isShade()
+
+ // This is true after completing a transition to communal.
+ val isIdleOnCommunal = transitionState.isIdle(Scenes.Communal)
+
+ // This is true during the process of an unlock of the device.
+ // TODO(b/330587738): add support for remote unlock animations. If such an
+ // animation is underway, unlocking should be true.
+ val unlocking =
+ isOnKeyguard &&
+ (biometricUnlockState.mode == BiometricUnlockMode.WAKE_AND_UNLOCK ||
+ isTransitioningAwayFromKeyguard)
+
+ if (alternateBouncerInteractor.isVisibleState()) {
+ // This will cancel the keyguardFadingAway animation if it is running. We
+ // need to do this as otherwise it can remain pending and leave keyguard in
+ // a weird state.
+ onKeyguardFadedAway(isTransitioningAwayFromKeyguard)
+ if (!isTransitioningToShade || (isOccluded && !isAnyShadeExpanded)) {
+ // Safeguard which prevents the scrim from being stuck in the wrong
+ // state
+ Model(scrimState = ScrimState.KEYGUARD, unlocking = unlocking)
+ } else {
+ // Assume scrim state for shade is already correct and do nothing
+ null
+ }
+ } else if (isCurrentSceneBouncer && !unlocking) {
+ // Bouncer needs the front scrim when it's on top of an activity, tapping on
+ // a notification, editing QS or being dismissed by
+ // FLAG_DISMISS_KEYGUARD_ACTIVITY.
+ Model(
+ scrimState =
+ if (statusBarKeyguardViewManager.primaryBouncerNeedsScrimming()) {
+ ScrimState.BOUNCER_SCRIMMED
+ } else {
+ ScrimState.BOUNCER
+ },
+ unlocking = false,
+ )
+ } else if (isBrightnessMirrorVisible) {
+ Model(scrimState = ScrimState.BRIGHTNESS_MIRROR, unlocking = unlocking)
+ } else if (isCurrentSceneShade && !isDeviceEntered) {
+ Model(scrimState = ScrimState.SHADE_LOCKED, unlocking = unlocking)
+ } else if (isPulsing) {
+ Model(scrimState = ScrimState.PULSING, unlocking = unlocking)
+ } else if (dozeServiceHost.hasPendingScreenOffCallback()) {
+ Model(scrimState = ScrimState.OFF, unlocking = unlocking)
+ } else if (isDozing && !unlocking) {
+ // This will cancel the keyguardFadingAway animation if it is running. We
+ // need to do this as otherwise it can remain pending and leave keyguard in
+ // a weird state.
+ onKeyguardFadedAway(isTransitioningAwayFromKeyguard)
+ Model(scrimState = ScrimState.AOD, unlocking = false)
+ } else if (isIdleOnCommunal) {
+ if (isOnKeyguard && isDreaming && !unlocking) {
+ Model(
+ scrimState = ScrimState.GLANCEABLE_HUB_OVER_DREAM,
+ unlocking = false
+ )
+ } else {
+ Model(scrimState = ScrimState.GLANCEABLE_HUB, unlocking = unlocking)
+ }
+ } else if (isOnKeyguard && !unlocking && !isOccluded) {
+ Model(scrimState = ScrimState.KEYGUARD, unlocking = false)
+ } else if (isOnKeyguard && !unlocking && isDreaming) {
+ Model(scrimState = ScrimState.DREAMING, unlocking = false)
+ } else {
+ Model(scrimState = ScrimState.UNLOCKED, unlocking = unlocking)
+ }
+ }
+ .filterNotNull()
+ .collect { model ->
+ scrimController.setExpansionAffectsAlpha(!model.unlocking)
+ scrimController.transitionTo(model.scrimState)
+ }
+ }
+ }
+
+ private fun onKeyguardFadedAway(isKeyguardGoingAway: Boolean) {
+ if (isKeyguardGoingAway) {
+ statusBarKeyguardViewManager.onKeyguardFadedAway()
+ }
+ }
+
+ private fun SceneKey.isKeyguard(): Boolean {
+ return this == Scenes.Lockscreen || this == Scenes.Bouncer
+ }
+
+ private fun SceneKey.isShade(): Boolean {
+ return this == Scenes.Shade ||
+ this == Scenes.QuickSettings ||
+ this == Scenes.NotificationsShade ||
+ this == Scenes.QuickSettingsShade
+ }
+
+ private data class Model(
+ val scrimState: ScrimState,
+ val unlocking: Boolean,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 37f2a21..49810762 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -137,14 +137,14 @@
public void startObserving() {
if (!mObserving) {
mObserving = true;
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
BRIGHTNESS_MODE_URI,
false, this, UserHandle.USER_ALL);
}
}
public void stopObserving() {
- mSecureSettings.unregisterContentObserver(this);
+ mSecureSettings.unregisterContentObserverSync(this);
mObserving = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
index 5e4b173..a171d33 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
@@ -381,7 +381,10 @@
mLogger.logHandleUpdateCarrierInfo(info);
mNoSimTextView.setVisibility(View.GONE);
- if (!info.airplaneMode && info.anySimReady) {
+ if (info.isInSatelliteMode) {
+ mLogger.logUsingSatelliteText(info.carrierText);
+ showSingleText(info.carrierText);
+ } else if (!info.airplaneMode && info.anySimReady) {
boolean[] slotSeen = new boolean[SIM_SLOTS];
if (info.listOfCarriers.length == info.subscriptionIds.length) {
mLogger.logUsingSimViews();
@@ -416,22 +419,31 @@
info.listOfCarriers.length, info.subscriptionIds.length);
}
} else {
+ // No sims or airplane mode (but not WFC), so just show the main carrier text.
mLogger.logUsingNoSimView(info.carrierText);
- // No sims or airplane mode (but not WFC). Do not show ShadeCarrierGroup,
- // instead just show info.carrierText in a different view.
- for (int i = 0; i < SIM_SLOTS; i++) {
- mInfos[i] = mInfos[i].changeVisibility(false);
- mCarrierGroups[i].setCarrierText("");
- mCarrierGroups[i].setVisibility(View.GONE);
- }
- mNoSimTextView.setText(info.carrierText);
- if (!TextUtils.isEmpty(info.carrierText)) {
- mNoSimTextView.setVisibility(View.VISIBLE);
- }
+ showSingleText(info.carrierText);
}
handleUpdateState(); // handleUpdateCarrierInfo is always called from main thread.
}
+ /**
+ * Shows only the given text in a single TextView and hides ShadeCarrierGroup (which would show
+ * individual SIM details).
+ */
+ private void showSingleText(CharSequence text) {
+ for (int i = 0; i < SIM_SLOTS; i++) {
+ mInfos[i] = mInfos[i].changeVisibility(false);
+ mCarrierGroups[i].setCarrierText("");
+ mCarrierGroups[i].setVisibility(View.GONE);
+ }
+ // TODO(b/341841138): Re-name this view now that it's being used for more than just the
+ // no-SIM case.
+ mNoSimTextView.setText(text);
+ if (!TextUtils.isEmpty(text)) {
+ mNoSimTextView.setVisibility(View.VISIBLE);
+ }
+ }
+
private static class H extends Handler {
private Consumer<CarrierTextManager.CarrierTextCallbackInfo> mUpdateCarrierInfo;
private Runnable mUpdateState;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerLogger.kt
index af06a35..b563cd9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerLogger.kt
@@ -65,6 +65,15 @@
)
}
+ fun logUsingSatelliteText(text: CharSequence) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ { str1 = "$text" },
+ { "â”— updating No SIM view with satellite text=$str1" },
+ )
+ }
+
fun logUsingSimViews() {
buffer.log(TAG, LogLevel.VERBOSE, {}, { "â”— updating SIM views" })
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
index 9885fe4..e7fc18e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.domain.interactor
import com.android.keyguard.LockIconViewController
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
@@ -30,7 +31,8 @@
class ShadeLockscreenInteractorImpl
@Inject
constructor(
- @Background private val scope: CoroutineScope,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundScope: CoroutineScope,
private val shadeInteractor: ShadeInteractor,
private val sceneInteractor: SceneInteractor,
private val lockIconViewController: LockIconViewController,
@@ -68,7 +70,7 @@
// Now handled elsewhere. Do nothing.
}
override fun transitionToExpandedShade(delay: Long) {
- scope.launch {
+ backgroundScope.launch {
delay(delay)
changeToShadeScene()
}
@@ -96,10 +98,12 @@
}
private fun changeToShadeScene() {
- val shadeMode = shadeInteractor.shadeMode.value
- sceneInteractor.changeScene(
- if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade,
- "ShadeLockscreenInteractorImpl.expandToNotifications",
- )
+ applicationScope.launch {
+ val shadeMode = shadeInteractor.shadeMode.value
+ sceneInteractor.changeScene(
+ if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade,
+ "ShadeLockscreenInteractorImpl.expandToNotifications",
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
index 95768e5..2dfc920 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
@@ -318,10 +318,10 @@
};
// Register to listen for changes in Settings.Secure settings.
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS, mContentObserver,
UserHandle.USER_CURRENT);
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
Settings.Secure.USER_SETUP_COMPLETE, mContentObserver,
UserHandle.USER_CURRENT);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 455c964..2e87a5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -143,6 +143,12 @@
private var managedUserHandle: UserHandle? = null
private var mSplitShadeEnabled = false
+ var suppressDisconnects = false
+ set(value) {
+ field = value
+ disconnect()
+ }
+
// TODO(b/202758428): refactor so that we can test color updates via region samping, similar to
// how we test color updates when theme changes (See testThemeChangeUpdatesTextColor).
@@ -522,6 +528,7 @@
*/
fun disconnect() {
if (!smartspaceViews.isEmpty()) return
+ if (suppressDisconnects) return
execution.assertIsMainThread()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 42bf4e7..071192b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -289,8 +289,9 @@
// for each change, lookup the new value
.map {
secureSettings.getIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- UserHandle.USER_CURRENT,
+ name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ def = 0,
+ userHandle = UserHandle.USER_CURRENT,
) == 1
}
// don't emit anything if nothing has changed
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
index 4c2ef83..4c82bc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
@@ -37,11 +37,13 @@
import javax.inject.Inject
/**
- * A class which provides an adjustment object to the preparation coordinator which is uses
- * to ensure that notifications are reinflated when ranking-derived information changes.
+ * A class which provides an adjustment object to the preparation coordinator which is uses to
+ * ensure that notifications are reinflated when ranking-derived information changes.
*/
@SysUISingleton
-class NotifUiAdjustmentProvider @Inject constructor(
+class NotifUiAdjustmentProvider
+@Inject
+constructor(
@Main private val handler: Handler,
private val secureSettings: SecureSettings,
private val lockscreenUserManager: NotificationLockscreenUserManager,
@@ -53,14 +55,13 @@
private val dirtyListeners = ListenerSet<Runnable>()
private var isSnoozeSettingsEnabled = false
- /**
- * Update the snooze enabled value on user switch
- */
- private val userTrackerCallback = object : UserTracker.Callback {
- override fun onUserChanged(newUser: Int, userContext: Context) {
- updateSnoozeEnabled()
+ /** Update the snooze enabled value on user switch */
+ private val userTrackerCallback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ updateSnoozeEnabled()
+ }
}
- }
init {
userTracker.addCallback(userTrackerCallback, HandlerExecutor(handler))
@@ -75,7 +76,7 @@
)
}
updateSnoozeEnabled()
- secureSettings.registerContentObserverForUser(
+ secureSettings.registerContentObserverForUserSync(
SHOW_NOTIFICATION_SNOOZE,
settingsObserver,
UserHandle.USER_ALL
@@ -93,7 +94,7 @@
onSensitiveStateChangedListener
)
}
- secureSettings.unregisterContentObserver(settingsObserver)
+ secureSettings.unregisterContentObserverSync(settingsObserver)
}
}
@@ -104,12 +105,13 @@
private val onSensitiveStateChangedListener = Runnable { dirtyListeners.forEach(Runnable::run) }
- private val settingsObserver = object : ContentObserver(handler) {
- override fun onChange(selfChange: Boolean) {
- updateSnoozeEnabled()
- dirtyListeners.forEach(Runnable::run)
+ private val settingsObserver =
+ object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean) {
+ updateSnoozeEnabled()
+ dirtyListeners.forEach(Runnable::run)
+ }
}
- }
private fun updateSnoozeEnabled() {
isSnoozeSettingsEnabled =
@@ -126,22 +128,23 @@
}
/**
- * Returns a adjustment object for the given entry. This can be compared to a previous instance
+ * Returns a adjustment object for the given entry. This can be compared to a previous instance
* from the same notification using [NotifUiAdjustment.needReinflate] to determine if it should
* be reinflated.
*/
- fun calculateAdjustment(entry: NotificationEntry) = NotifUiAdjustment(
- key = entry.key,
- smartActions = entry.ranking.smartActions,
- smartReplies = entry.ranking.smartReplies,
- isConversation = entry.ranking.isConversation,
- isSnoozeEnabled = isSnoozeSettingsEnabled && !entry.isCanceled,
- isMinimized = isEntryMinimized(entry),
- needsRedaction =
- lockscreenUserManager.needsRedaction(entry) ||
- (screenshareNotificationHiding() &&
- sensitiveNotifProtectionController.shouldProtectNotification(entry)),
- isChildInGroup = entry.hasEverBeenGroupChild(),
- isGroupSummary = entry.hasEverBeenGroupSummary(),
- )
+ fun calculateAdjustment(entry: NotificationEntry) =
+ NotifUiAdjustment(
+ key = entry.key,
+ smartActions = entry.ranking.smartActions,
+ smartReplies = entry.ranking.smartReplies,
+ isConversation = entry.ranking.isConversation,
+ isSnoozeEnabled = isSnoozeSettingsEnabled && !entry.isCanceled,
+ isMinimized = isEntryMinimized(entry),
+ needsRedaction =
+ lockscreenUserManager.needsRedaction(entry) ||
+ (screenshareNotificationHiding() &&
+ sensitiveNotifProtectionController.shouldProtectNotification(entry)),
+ isChildInGroup = entry.hasEverBeenGroupChild(),
+ isGroupSummary = entry.hasEverBeenGroupSummary(),
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 42a5bdf..f84b5f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -33,8 +33,8 @@
import android.provider.Settings
import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
import android.provider.Settings.Global.HEADS_UP_OFF
+import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.UiEvent;
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
@@ -86,7 +86,7 @@
}
}
- globalSettings.registerContentObserver(
+ globalSettings.registerContentObserverSync(
globalSettings.getUriFor(HEADS_UP_NOTIFICATIONS_ENABLED),
/* notifyForDescendants = */ true,
observer
@@ -94,7 +94,7 @@
// QQQ: Do we need to register for SETTING_HEADS_UP_TICKER? It seems unused.
- observer.onChange(/* selfChange = */ true)
+ observer.onChange(/* selfChange= */ true)
}
}
@@ -243,6 +243,7 @@
override fun shouldSuppress(entry: NotificationEntry) =
keyguardNotificationVisibilityProvider.shouldHideNotification(entry)
}
+
class AvalancheSuppressor(
private val avalancheProvider: AvalancheProvider,
private val systemClock: SystemClock,
@@ -268,38 +269,33 @@
SUPPRESS
}
- enum class AvalancheEvent(private val id: Int) : UiEventLogger.UiEventEnum {
- @UiEvent(doc = "An avalanche event occurred but this notification was suppressed by a " +
- "non-avalanche suppressor.")
- START(1802),
-
- @UiEvent(doc = "HUN was suppressed in avalanche.")
- SUPPRESS(1803),
-
- @UiEvent(doc = "HUN allowed during avalanche because it is high priority.")
- ALLOW_CONVERSATION_AFTER_AVALANCHE(1804),
-
- @UiEvent(doc = "HUN allowed during avalanche because it is a high priority conversation.")
- ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME(1805),
-
- @UiEvent(doc = "HUN allowed during avalanche because it is a call.")
- ALLOW_CALLSTYLE(1806),
-
- @UiEvent(doc = "HUN allowed during avalanche because it is a calendar notification.")
- ALLOW_CATEGORY_REMINDER(1807),
-
- @UiEvent(doc = "HUN allowed during avalanche because it is a calendar notification.")
- ALLOW_CATEGORY_EVENT(1808),
-
- @UiEvent(doc = "HUN allowed during avalanche because it has a full screen intent and " +
- "the full screen intent permission is granted.")
- ALLOW_FSI_WITH_PERMISSION_ON(1809),
-
- @UiEvent(doc = "HUN allowed during avalanche because it is colorized.")
- ALLOW_COLORIZED(1810),
-
- @UiEvent(doc = "HUN allowed during avalanche because it is an emergency notification.")
- ALLOW_EMERGENCY(1811);
+ enum class AvalancheEvent(private val id: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(
+ doc =
+ "An avalanche event occurred but this notification was suppressed by a " +
+ "non-avalanche suppressor."
+ )
+ START(1802),
+ @UiEvent(doc = "HUN was suppressed in avalanche.") SUPPRESS(1803),
+ @UiEvent(doc = "HUN allowed during avalanche because it is high priority.")
+ ALLOW_CONVERSATION_AFTER_AVALANCHE(1804),
+ @UiEvent(doc = "HUN allowed during avalanche because it is a high priority conversation.")
+ ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME(1805),
+ @UiEvent(doc = "HUN allowed during avalanche because it is a call.") ALLOW_CALLSTYLE(1806),
+ @UiEvent(doc = "HUN allowed during avalanche because it is a calendar notification.")
+ ALLOW_CATEGORY_REMINDER(1807),
+ @UiEvent(doc = "HUN allowed during avalanche because it is a calendar notification.")
+ ALLOW_CATEGORY_EVENT(1808),
+ @UiEvent(
+ doc =
+ "HUN allowed during avalanche because it has a full screen intent and " +
+ "the full screen intent permission is granted."
+ )
+ ALLOW_FSI_WITH_PERMISSION_ON(1809),
+ @UiEvent(doc = "HUN allowed during avalanche because it is colorized.")
+ ALLOW_COLORIZED(1810),
+ @UiEvent(doc = "HUN allowed during avalanche because it is an emergency notification.")
+ ALLOW_EMERGENCY(1811);
override fun getId(): Int {
return id
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index 332ece4..c7548aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -14,7 +14,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -41,8 +40,8 @@
/** Determines if notifications should be visible based on the state of the keyguard. */
interface KeyguardNotificationVisibilityProvider {
/**
- * Determines if the given notification should be hidden based on the current keyguard state.
- * If a [Consumer] registered via [addOnStateChangedListener] is invoked, the results of this
+ * Determines if the given notification should be hidden based on the current keyguard state. If
+ * a [Consumer] registered via [addOnStateChangedListener] is invoked, the results of this
* method may no longer be valid and should be re-queried.
*/
fun shouldHideNotification(entry: NotificationEntry): Boolean
@@ -61,8 +60,9 @@
@Module
interface KeyguardNotificationVisibilityProviderImplModule {
@Binds
- fun bindImpl(impl: KeyguardNotificationVisibilityProviderImpl):
- KeyguardNotificationVisibilityProvider
+ fun bindImpl(
+ impl: KeyguardNotificationVisibilityProviderImpl
+ ): KeyguardNotificationVisibilityProvider
@Binds
@IntoMap
@@ -71,7 +71,9 @@
}
@SysUISingleton
-class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
+class KeyguardNotificationVisibilityProviderImpl
+@Inject
+constructor(
@Main private val handler: Handler,
private val keyguardStateController: KeyguardStateController,
private val lockscreenUserManager: NotificationLockscreenUserManager,
@@ -84,76 +86,88 @@
private val featureFlags: FeatureFlagsClassic
) : CoreStartable, KeyguardNotificationVisibilityProvider {
private val showSilentNotifsUri =
- secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS)
+ secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS)
private val onStateChangedListeners = ListenerSet<Consumer<String>>()
private var hideSilentNotificationsOnLockscreen: Boolean = false
- private val userTrackerCallback = object : UserTracker.Callback {
- override fun onUserChanged(newUser: Int, userContext: Context) {
- readShowSilentNotificationSetting()
- if (isLockedOrLocking) {
- // maybe public mode changed
- notifyStateChanged("onUserSwitched")
+ private val userTrackerCallback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ readShowSilentNotificationSetting()
+ if (isLockedOrLocking) {
+ // maybe public mode changed
+ notifyStateChanged("onUserSwitched")
+ }
}
}
- }
override fun start() {
readShowSilentNotificationSetting()
- keyguardStateController.addCallback(object : KeyguardStateController.Callback {
- override fun onUnlockedChanged() {
- notifyStateChanged("onUnlockedChanged")
- }
+ keyguardStateController.addCallback(
+ object : KeyguardStateController.Callback {
+ override fun onUnlockedChanged() {
+ notifyStateChanged("onUnlockedChanged")
+ }
- override fun onKeyguardShowingChanged() {
- notifyStateChanged("onKeyguardShowingChanged")
+ override fun onKeyguardShowingChanged() {
+ notifyStateChanged("onKeyguardShowingChanged")
+ }
}
- })
- keyguardUpdateMonitor.registerCallback(object : KeyguardUpdateMonitorCallback() {
- override fun onStrongAuthStateChanged(userId: Int) {
- notifyStateChanged("onStrongAuthStateChanged")
+ )
+ keyguardUpdateMonitor.registerCallback(
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onStrongAuthStateChanged(userId: Int) {
+ notifyStateChanged("onStrongAuthStateChanged")
+ }
}
- })
+ )
// register lockscreen settings changed callbacks:
- val settingsObserver: ContentObserver = object : ContentObserver(handler) {
- override fun onChange(selfChange: Boolean, uri: Uri?) {
- if (uri == showSilentNotifsUri) {
- readShowSilentNotificationSetting()
- }
- if (isLockedOrLocking) {
- notifyStateChanged("Settings $uri changed")
+ val settingsObserver: ContentObserver =
+ object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
+ if (uri == showSilentNotifsUri) {
+ readShowSilentNotificationSetting()
+ }
+ if (isLockedOrLocking) {
+ notifyStateChanged("Settings $uri changed")
+ }
}
}
- }
- secureSettings.registerContentObserverForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
- settingsObserver,
- UserHandle.USER_ALL)
+ secureSettings.registerContentObserverForUserSync(
+ Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ settingsObserver,
+ UserHandle.USER_ALL
+ )
- secureSettings.registerContentObserverForUser(
- Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
- true,
- settingsObserver,
- UserHandle.USER_ALL)
+ secureSettings.registerContentObserverForUserSync(
+ Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ true,
+ settingsObserver,
+ UserHandle.USER_ALL
+ )
- globalSettings.registerContentObserver(Settings.Global.ZEN_MODE, settingsObserver)
+ globalSettings.registerContentObserverSync(Settings.Global.ZEN_MODE, settingsObserver)
- secureSettings.registerContentObserverForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
- settingsObserver,
- UserHandle.USER_ALL)
+ secureSettings.registerContentObserverForUserSync(
+ Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
+ settingsObserver,
+ UserHandle.USER_ALL
+ )
// register (maybe) public mode changed callbacks:
- statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
- override fun onStateChanged(newState: Int) {
- notifyStateChanged("onStatusBarStateChanged")
+ statusBarStateController.addCallback(
+ object : StatusBarStateController.StateListener {
+ override fun onStateChanged(newState: Int) {
+ notifyStateChanged("onStatusBarStateChanged")
+ }
+
+ override fun onUpcomingStateChanged(state: Int) {
+ notifyStateChanged("onStatusBarUpcomingStateChanged")
+ }
}
- override fun onUpcomingStateChanged(state: Int) {
- notifyStateChanged("onStatusBarUpcomingStateChanged")
- }
- })
+ )
userTracker.addCallback(userTrackerCallback, HandlerExecutor(handler))
}
@@ -169,45 +183,48 @@
onStateChangedListeners.forEach { it.accept(reason) }
}
- override fun shouldHideNotification(entry: NotificationEntry): Boolean = when {
- // Keyguard state doesn't matter if the keyguard is not showing.
- !isLockedOrLocking -> false
- // Notifications not allowed on the lockscreen, always hide.
- !lockscreenUserManager.shouldShowLockscreenNotifications() -> true
- // User settings do not allow this notification on the lockscreen, so hide it.
- userSettingsDisallowNotification(entry) -> true
- // Entry is explicitly marked SECRET, so hide it.
- entry.sbn.notification.visibility == VISIBILITY_SECRET -> true
- // if entry is silent, apply custom logic to see if should hide
- shouldHideIfEntrySilent(entry) -> true
- else -> false
- }
+ override fun shouldHideNotification(entry: NotificationEntry): Boolean =
+ when {
+ // Keyguard state doesn't matter if the keyguard is not showing.
+ !isLockedOrLocking -> false
+ // Notifications not allowed on the lockscreen, always hide.
+ !lockscreenUserManager.shouldShowLockscreenNotifications() -> true
+ // User settings do not allow this notification on the lockscreen, so hide it.
+ userSettingsDisallowNotification(entry) -> true
+ // Entry is explicitly marked SECRET, so hide it.
+ entry.sbn.notification.visibility == VISIBILITY_SECRET -> true
+ // if entry is silent, apply custom logic to see if should hide
+ shouldHideIfEntrySilent(entry) -> true
+ else -> false
+ }
- private fun shouldHideIfEntrySilent(entry: ListEntry): Boolean = when {
- // Show if explicitly high priority (not hidden)
- highPriorityProvider.isExplicitlyHighPriority(entry) -> false
- // Ambient notifications are hidden always from lock screen
- entry.representativeEntry?.isAmbient == true -> true
- // [Now notification is silent]
- // Hide regardless of parent priority if user wants silent notifs hidden
- hideSilentNotificationsOnLockscreen -> true
- // Parent priority is high enough to be shown on the lockscreen, do not hide.
- entry.parent?.let(::shouldHideIfEntrySilent) == false -> false
- // Show when silent notifications are allowed on lockscreen
- else -> false
- }
+ private fun shouldHideIfEntrySilent(entry: ListEntry): Boolean =
+ when {
+ // Show if explicitly high priority (not hidden)
+ highPriorityProvider.isExplicitlyHighPriority(entry) -> false
+ // Ambient notifications are hidden always from lock screen
+ entry.representativeEntry?.isAmbient == true -> true
+ // [Now notification is silent]
+ // Hide regardless of parent priority if user wants silent notifs hidden
+ hideSilentNotificationsOnLockscreen -> true
+ // Parent priority is high enough to be shown on the lockscreen, do not hide.
+ entry.parent?.let(::shouldHideIfEntrySilent) == false -> false
+ // Show when silent notifications are allowed on lockscreen
+ else -> false
+ }
private fun userSettingsDisallowNotification(entry: NotificationEntry): Boolean {
- fun disallowForUser(user: Int) = when {
- // user is in lockdown, always disallow
- keyguardUpdateMonitor.isUserInLockdown(user) -> true
- // device isn't public, no need to check public-related settings, so allow
- !lockscreenUserManager.isLockscreenPublicMode(user) -> false
- // entry is meant to be secret on the lockscreen, disallow
- isRankingVisibilitySecret(entry) -> true
- // disallow if user disallows notifications in public
- else -> !lockscreenUserManager.userAllowsNotificationsInPublic(user)
- }
+ fun disallowForUser(user: Int) =
+ when {
+ // user is in lockdown, always disallow
+ keyguardUpdateMonitor.isUserInLockdown(user) -> true
+ // device isn't public, no need to check public-related settings, so allow
+ !lockscreenUserManager.isLockscreenPublicMode(user) -> false
+ // entry is meant to be secret on the lockscreen, disallow
+ isRankingVisibilitySecret(entry) -> true
+ // disallow if user disallows notifications in public
+ else -> !lockscreenUserManager.userAllowsNotificationsInPublic(user)
+ }
val currentUser = lockscreenUserManager.currentUserId
val notifUser = entry.sbn.user.identifier
return when {
@@ -222,28 +239,35 @@
// ranking.lockscreenVisibilityOverride contains possibly out of date DPC and Setting
// info, and NotificationLockscreenUserManagerImpl is already listening for updates
// to those
- return entry.ranking.channel != null && entry.ranking.channel.lockscreenVisibility ==
- VISIBILITY_SECRET
+ return entry.ranking.channel != null &&
+ entry.ranking.channel.lockscreenVisibility == VISIBILITY_SECRET
}
- override fun dump(pw: PrintWriter, args: Array<out String>) = pw.asIndenting().run {
- println("isLockedOrLocking", isLockedOrLocking)
- withIncreasedIndent {
- println("keyguardStateController.isShowing", keyguardStateController.isShowing)
- println("statusBarStateController.currentOrUpcomingState",
- statusBarStateController.currentOrUpcomingState)
+ override fun dump(pw: PrintWriter, args: Array<out String>) =
+ pw.asIndenting().run {
+ println("isLockedOrLocking", isLockedOrLocking)
+ withIncreasedIndent {
+ println("keyguardStateController.isShowing", keyguardStateController.isShowing)
+ println(
+ "statusBarStateController.currentOrUpcomingState",
+ statusBarStateController.currentOrUpcomingState
+ )
+ }
+ println("hideSilentNotificationsOnLockscreen", hideSilentNotificationsOnLockscreen)
}
- println("hideSilentNotificationsOnLockscreen", hideSilentNotificationsOnLockscreen)
- }
- private val isLockedOrLocking get() =
- keyguardStateController.isShowing ||
+ private val isLockedOrLocking
+ get() =
+ keyguardStateController.isShowing ||
statusBarStateController.currentOrUpcomingState == StatusBarState.KEYGUARD
private fun readShowSilentNotificationSetting() {
val showSilentNotifs =
- secureSettings.getBoolForUser(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
- false, UserHandle.USER_CURRENT)
+ secureSettings.getBoolForUser(
+ Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
+ false,
+ UserHandle.USER_CURRENT
+ )
hideSilentNotificationsOnLockscreen = !showSilentNotifs
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 74925c8..fea360d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -171,11 +171,11 @@
};
if (ENABLE_HEADS_UP) {
- mGlobalSettings.registerContentObserver(
+ mGlobalSettings.registerContentObserverSync(
mGlobalSettings.getUriFor(HEADS_UP_NOTIFICATIONS_ENABLED),
true,
headsUpObserver);
- mGlobalSettings.registerContentObserver(
+ mGlobalSettings.registerContentObserverSync(
mGlobalSettings.getUriFor(SETTING_HEADS_UP_TICKER), true,
headsUpObserver);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
index 0fdf681..1efa56f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
@@ -20,8 +20,6 @@
import android.util.Log
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import dagger.Lazy
import javax.inject.Inject
@@ -30,7 +28,6 @@
class NotificationMemoryMonitor
@Inject
constructor(
- private val featureFlags: FeatureFlags,
private val notificationMemoryDumper: NotificationMemoryDumper,
private val notificationMemoryLogger: Lazy<NotificationMemoryLogger>,
) : CoreStartable {
@@ -42,9 +39,6 @@
override fun start() {
Log.d(TAG, "NotificationMemoryMonitor initialized.")
notificationMemoryDumper.init()
- if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_LOGGING_ENABLED)) {
- Log.d(TAG, "Notification memory logging enabled.")
- notificationMemoryLogger.get().init()
- }
+ notificationMemoryLogger.get().init()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java
index a17c066..30dbfed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java
@@ -92,9 +92,9 @@
synchronized (mListeners) {
if (mListeners.size() > 0) {
- mSecureSettings.unregisterContentObserver(mContentObserver);
+ mSecureSettings.unregisterContentObserverSync(mContentObserver);
for (Uri uri : mListeners.keySet()) {
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
uri, false, mContentObserver, newUser);
}
}
@@ -131,7 +131,7 @@
mListeners.put(uri, currentListeners);
if (currentListeners.size() == 1) {
mBackgroundHandler.post(() -> {
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
uri, false, mContentObserver, mUserTracker.getUserId());
});
}
@@ -159,7 +159,7 @@
if (mListeners.size() == 0) {
mBackgroundHandler.post(() -> {
- mSecureSettings.unregisterContentObserver(mContentObserver);
+ mSecureSettings.unregisterContentObserverSync(mContentObserver);
});
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationRowContentBinderRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationRowContentBinderRefactor.kt
new file mode 100644
index 0000000..997acdd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationRowContentBinderRefactor.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.notification.row.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notification row content binder refactor flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationRowContentBinderRefactor {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_ROW_CONTENT_BINDER_REFACTOR
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationRowContentBinderRefactor()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 78a8036..7567f36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2800,6 +2800,10 @@
@Override
@VisibleForTesting
public void updateScrimController() {
+ if (SceneContainerFlag.isEnabled()) {
+ return;
+ }
+
Trace.beginSection("CentralSurfaces#updateScrimController");
boolean unlocking = mKeyguardStateController.isShowing() && (
@@ -2822,15 +2826,15 @@
// Assume scrim state for shade is already correct and do nothing
} else {
// Safeguard which prevents the scrim from being stuck in the wrong state
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
}
} else {
if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded())
&& (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
|| mTransitionToFullShadeProgress > 0f)) {
- mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
+ mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
} else {
- mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED);
+ mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED);
}
}
// This will cancel the keyguardFadingAway animation if it is running. We need to do
@@ -2842,40 +2846,40 @@
// FLAG_DISMISS_KEYGUARD_ACTIVITY.
ScrimState state = mStatusBarKeyguardViewManager.primaryBouncerNeedsScrimming()
? ScrimState.BOUNCER_SCRIMMED : ScrimState.BOUNCER;
- mScrimController.transitionTo(state);
+ mScrimController.legacyTransitionTo(state);
} else if (mBrightnessMirrorVisible) {
- mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+ mScrimController.legacyTransitionTo(ScrimState.BRIGHTNESS_MIRROR);
} else if (mState == StatusBarState.SHADE_LOCKED) {
- mScrimController.transitionTo(ScrimState.SHADE_LOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.SHADE_LOCKED);
} else if (mDozeServiceHost.isPulsing()) {
- mScrimController.transitionTo(ScrimState.PULSING,
+ mScrimController.legacyTransitionTo(ScrimState.PULSING,
mDozeScrimController.getScrimCallback());
} else if (mDozeServiceHost.hasPendingScreenOffCallback()) {
- mScrimController.transitionTo(ScrimState.OFF, new ScrimController.Callback() {
+ mScrimController.legacyTransitionTo(ScrimState.OFF, new ScrimController.Callback() {
@Override
public void onFinished() {
mDozeServiceHost.executePendingScreenOffCallback();
}
});
} else if (mDozing && !unlocking) {
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
// This will cancel the keyguardFadingAway animation if it is running. We need to do
// this as otherwise it can remain pending and leave keyguard in a weird state.
mUnlockScrimCallback.onCancelled();
} else if (mIsIdleOnCommunal) {
if (dreaming) {
- mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+ mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
} else {
- mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+ mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB);
}
} else if (mKeyguardStateController.isShowing()
&& !mKeyguardStateController.isOccluded()
&& !unlocking) {
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
} else if (dreaming) {
- mScrimController.transitionTo(ScrimState.DREAMING);
+ mScrimController.legacyTransitionTo(ScrimState.DREAMING);
} else {
- mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
}
updateLightRevealScrimVisibility();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 77f3706..ab097e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -448,7 +448,7 @@
/**
* When the dozing host is waiting for scrims to fade out to change the display state.
*/
- boolean hasPendingScreenOffCallback() {
+ public boolean hasPendingScreenOffCallback() {
return mPendingScreenOffCallback != null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 6d4301f..e44edcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -398,7 +398,7 @@
mSystemIconsContainer.setOnHoverListener(hoverListener);
mView.setOnApplyWindowInsetsListener(
(view, windowInsets) -> mView.updateWindowInsets(windowInsets, mInsetsProvider));
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
false,
mVolumeSettingObserver,
@@ -416,7 +416,7 @@
mStatusBarStateController.removeCallback(mStatusBarStateListener);
mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
mDisableStateTracker.stopTracking(mCommandQueue);
- mSecureSettings.unregisterContentObserver(mVolumeSettingObserver);
+ mSecureSettings.unregisterContentObserverSync(mVolumeSettingObserver);
if (mTintedIconManager != null) {
mStatusBarIconController.removeIconGroup(mTintedIconManager);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index fe001b3..9cece76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -72,6 +72,7 @@
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.shade.ShadeViewController;
@@ -319,8 +320,11 @@
mScrimBehind.setViewAlpha(mBehindAlpha);
};
+ @VisibleForTesting
Consumer<TransitionStep> mBouncerToGoneTransition;
+ private boolean mViewsAttached;
+
@Inject
public ScrimController(
LightBarController lightBarController,
@@ -432,6 +436,16 @@
state.prepare(state);
}
+ hydrateStateInternally(behindScrim);
+
+ mViewsAttached = true;
+ }
+
+ private void hydrateStateInternally(ScrimView behindScrim) {
+ if (SceneContainerFlag.isEnabled()) {
+ return;
+ }
+
// Directly control transition to UNLOCKED scrim state from PRIMARY_BOUNCER, and make sure
// to report back that keyguard has faded away. This fixes cases where the scrim state was
// rapidly switching on unlock, due to shifts in state in CentralSurfacesImpl
@@ -443,7 +457,7 @@
if (state == TransitionState.STARTED) {
setExpansionAffectsAlpha(false);
- transitionTo(ScrimState.UNLOCKED);
+ legacyTransitionTo(ScrimState.UNLOCKED);
}
if (state == TransitionState.FINISHED || state == TransitionState.CANCELED) {
@@ -508,10 +522,36 @@
}
public void transitionTo(ScrimState state) {
- transitionTo(state, null);
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode() || !mViewsAttached) {
+ return;
+ }
+
+ internalTransitionTo(state, null);
}
- public void transitionTo(ScrimState state, Callback callback) {
+ /**
+ * Transitions to the given {@link ScrimState}.
+ *
+ * @deprecated Legacy codepath only. Do not call directly.
+ */
+ @Deprecated
+ public void legacyTransitionTo(ScrimState state) {
+ SceneContainerFlag.assertInLegacyMode();
+ internalTransitionTo(state, null);
+ }
+
+ /**
+ * Transitions to the given {@link ScrimState}.
+ *
+ * @deprecated Legacy codepath only. Do not call directly.
+ */
+ @Deprecated
+ public void legacyTransitionTo(ScrimState state, Callback callback) {
+ SceneContainerFlag.assertInLegacyMode();
+ internalTransitionTo(state, callback);
+ }
+
+ private void internalTransitionTo(ScrimState state, Callback callback) {
if (mIsBouncerToGoneTransitionRunning) {
Log.i(TAG, "Skipping transition to: " + state
+ " while mIsBouncerToGoneTransitionRunning");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 479aef1..c53558e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -23,9 +23,9 @@
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -162,7 +162,7 @@
this.centralSurfaces = centralSurfaces
updateAnimatorDurationScale()
- globalSettings.registerContentObserver(
+ globalSettings.registerContentObserverSync(
Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
/* notify for descendants */ false,
animatorDurationScaleObserver
@@ -376,8 +376,9 @@
// We currently draw both the light reveal scrim, and the AOD UI, in the shade. If it's
// already expanded and showing notifications/QS, the animation looks really messy. For now,
// disable it if the notification panel is expanded.
- if ((!this::centralSurfaces.isInitialized ||
- panelExpansionInteractorLazy.get().isPanelExpanded) &&
+ if (
+ (!this::centralSurfaces.isInitialized ||
+ panelExpansionInteractorLazy.get().isPanelExpanded) &&
// Status bar might be expanded because we have started
// playing the animation already
!isAnimationPlaying()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index aac211a..3d8090d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -80,6 +80,10 @@
import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener;
import com.android.systemui.util.settings.SecureSettings;
+import kotlin.Unit;
+
+import kotlinx.coroutines.DisposableHandle;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -90,10 +94,6 @@
import javax.inject.Inject;
-import kotlin.Unit;
-
-import kotlinx.coroutines.DisposableHandle;
-
/**
* Contains the collapsed status bar and handles hiding/showing based on disable flags
* and keyguard state. Also manages lifecycle to make sure the views it contains are being
@@ -431,7 +431,7 @@
initOngoingCallChip();
mAnimationScheduler.addCallback(this);
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
false,
mVolumeSettingObserver,
@@ -445,7 +445,7 @@
mStatusBarStateController.removeCallback(this);
mOngoingCallController.removeCallback(mOngoingCallListener);
mAnimationScheduler.removeCallback(this);
- mSecureSettings.unregisterContentObserver(mVolumeSettingObserver);
+ mSecureSettings.unregisterContentObserverSync(mVolumeSettingObserver);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index c32f0e8..261258a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -40,6 +40,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
@@ -99,6 +100,7 @@
private val context: Context,
@Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
+ @Main private val mainDispatcher: CoroutineDispatcher,
airplaneModeRepository: AirplaneModeRepository,
// Some "wifi networks" should be rendered as a mobile connection, which is why the wifi
// repository is an input to the mobile repository.
@@ -315,6 +317,7 @@
trySend(false)
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
+ .flowOn(mainDispatcher)
.logDiffsForTable(
tableLogger,
LOGGING_PREFIX,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 4bd8681..fad5df8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -138,7 +138,7 @@
}
}
};
- globalSettings.registerContentObserver(
+ globalSettings.registerContentObserverSync(
globalSettings.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS),
/* notifyForDescendants = */ false,
settingsObserver);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
index 8b63dfe..8123f8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
@@ -42,16 +42,16 @@
import javax.inject.Inject
@SysUISingleton
-open class DeviceProvisionedControllerImpl @Inject constructor(
+open class DeviceProvisionedControllerImpl
+@Inject
+constructor(
private val secureSettings: SecureSettings,
private val globalSettings: GlobalSettings,
private val userTracker: UserTracker,
private val dumpManager: DumpManager,
@Background private val backgroundHandler: Handler,
@Main private val mainExecutor: Executor
-) : DeviceProvisionedController,
- DeviceProvisionedController.DeviceProvisionedListener,
- Dumpable {
+) : DeviceProvisionedController, DeviceProvisionedController.DeviceProvisionedListener, Dumpable {
companion object {
private const val ALL_USERS = -1
@@ -63,8 +63,7 @@
private val userSetupUri = secureSettings.getUriFor(Settings.Secure.USER_SETUP_COMPLETE)
private val deviceProvisioned = AtomicBoolean(false)
- @GuardedBy("lock")
- private val userSetupComplete = SparseBooleanArray()
+ @GuardedBy("lock") private val userSetupComplete = SparseBooleanArray()
@GuardedBy("lock")
private val listeners = ArraySet<DeviceProvisionedController.DeviceProvisionedListener>()
@@ -81,42 +80,42 @@
return _currentUser
}
- private val observer = object : ContentObserver(backgroundHandler) {
- override fun onChange(
- selfChange: Boolean,
- uris: MutableCollection<Uri>,
- flags: Int,
- userId: Int
- ) {
- val updateDeviceProvisioned = deviceProvisionedUri in uris
- val updateUser = if (userSetupUri in uris) userId else NO_USERS
- updateValues(updateDeviceProvisioned, updateUser)
- if (updateDeviceProvisioned) {
- onDeviceProvisionedChanged()
- }
- if (updateUser != NO_USERS) {
- onUserSetupChanged()
+ private val observer =
+ object : ContentObserver(backgroundHandler) {
+ override fun onChange(
+ selfChange: Boolean,
+ uris: MutableCollection<Uri>,
+ flags: Int,
+ userId: Int
+ ) {
+ val updateDeviceProvisioned = deviceProvisionedUri in uris
+ val updateUser = if (userSetupUri in uris) userId else NO_USERS
+ updateValues(updateDeviceProvisioned, updateUser)
+ if (updateDeviceProvisioned) {
+ onDeviceProvisionedChanged()
+ }
+ if (updateUser != NO_USERS) {
+ onUserSetupChanged()
+ }
}
}
- }
- private val userChangedCallback = object : UserTracker.Callback {
- @WorkerThread
- override fun onUserChanged(newUser: Int, userContext: Context) {
- updateValues(updateDeviceProvisioned = false, updateUser = newUser)
- onUserSwitched()
+ private val userChangedCallback =
+ object : UserTracker.Callback {
+ @WorkerThread
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ updateValues(updateDeviceProvisioned = false, updateUser = newUser)
+ onUserSwitched()
+ }
+
+ override fun onProfilesChanged(profiles: List<UserInfo>) {}
}
- override fun onProfilesChanged(profiles: List<UserInfo>) {}
- }
-
init {
userSetupComplete.put(currentUser, false)
}
- /**
- * Call to initialize values and register observers
- */
+ /** Call to initialize values and register observers */
open fun init() {
if (!initted.compareAndSet(false, true)) {
return
@@ -124,31 +123,39 @@
dumpManager.registerDumpable(this)
updateValues()
userTracker.addCallback(userChangedCallback, backgroundExecutor)
- globalSettings.registerContentObserver(deviceProvisionedUri, observer)
- secureSettings.registerContentObserverForUser(userSetupUri, observer, UserHandle.USER_ALL)
+ globalSettings.registerContentObserverSync(deviceProvisionedUri, observer)
+ secureSettings.registerContentObserverForUserSync(
+ userSetupUri,
+ observer,
+ UserHandle.USER_ALL
+ )
}
@WorkerThread
- private fun updateValues(
- updateDeviceProvisioned: Boolean = true,
- updateUser: Int = ALL_USERS
- ) {
+ private fun updateValues(updateDeviceProvisioned: Boolean = true, updateUser: Int = ALL_USERS) {
if (updateDeviceProvisioned) {
- deviceProvisioned
- .set(globalSettings.getInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0)
+ deviceProvisioned.set(globalSettings.getInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0)
}
synchronized(lock) {
if (updateUser == ALL_USERS) {
val n = userSetupComplete.size()
for (i in 0 until n) {
val user = userSetupComplete.keyAt(i)
- val value = secureSettings
- .getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, user) != 0
+ val value =
+ secureSettings.getIntForUser(
+ Settings.Secure.USER_SETUP_COMPLETE,
+ 0,
+ user
+ ) != 0
userSetupComplete.put(user, value)
}
} else if (updateUser != NO_USERS) {
- val value = secureSettings
- .getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, updateUser) != 0
+ val value =
+ secureSettings.getIntForUser(
+ Settings.Secure.USER_SETUP_COMPLETE,
+ 0,
+ updateUser
+ ) != 0
userSetupComplete.put(updateUser, value)
}
}
@@ -160,15 +167,11 @@
* The listener will not be called when this happens.
*/
override fun addCallback(listener: DeviceProvisionedController.DeviceProvisionedListener) {
- synchronized(lock) {
- listeners.add(listener)
- }
+ synchronized(lock) { listeners.add(listener) }
}
override fun removeCallback(listener: DeviceProvisionedController.DeviceProvisionedListener) {
- synchronized(lock) {
- listeners.remove(listener)
- }
+ synchronized(lock) { listeners.remove(listener) }
}
override fun isDeviceProvisioned(): Boolean {
@@ -176,20 +179,14 @@
}
override fun isUserSetup(user: Int): Boolean {
- val index = synchronized(lock) {
- userSetupComplete.indexOfKey(user)
- }
+ val index = synchronized(lock) { userSetupComplete.indexOfKey(user) }
return if (index < 0) {
- val value = secureSettings
- .getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, user) != 0
- synchronized(lock) {
- userSetupComplete.put(user, value)
- }
+ val value =
+ secureSettings.getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, user) != 0
+ synchronized(lock) { userSetupComplete.put(user, value) }
value
} else {
- synchronized(lock) {
- userSetupComplete.get(user, false)
- }
+ synchronized(lock) { userSetupComplete.get(user, false) }
}
}
@@ -214,12 +211,8 @@
protected fun dispatchChange(
callback: DeviceProvisionedController.DeviceProvisionedListener.() -> Unit
) {
- val listenersCopy = synchronized(lock) {
- ArrayList(listeners)
- }
- mainExecutor.execute {
- listenersCopy.forEach(callback)
- }
+ val listenersCopy = synchronized(lock) { ArrayList(listeners) }
+ mainExecutor.execute { listenersCopy.forEach(callback) }
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
@@ -229,4 +222,4 @@
pw.println("Listeners: $listeners")
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 9eee5d0..f57b696 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -115,7 +115,7 @@
};
// Register to listen for changes in Settings.Secure settings.
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, mContentObserver, UserHandle.USER_ALL);
// Register to listen for changes in DeviceConfig settings.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
index 40bb67f..9ab8175 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -188,7 +188,7 @@
});
}
};
- settings.registerContentObserver(
+ settings.registerContentObserverSync(
DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS,
developerOptionsObserver);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 600005b..e09e577 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -140,13 +140,13 @@
mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (registerZenModeContentObserverBackground()) {
bgHandler.post(() -> {
- globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver);
- globalSettings.registerContentObserver(Global.ZEN_MODE_CONFIG_ETAG,
+ globalSettings.registerContentObserverSync(Global.ZEN_MODE, modeContentObserver);
+ globalSettings.registerContentObserverSync(Global.ZEN_MODE_CONFIG_ETAG,
configContentObserver);
});
} else {
- globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver);
- globalSettings.registerContentObserver(Global.ZEN_MODE_CONFIG_ETAG,
+ globalSettings.registerContentObserverSync(Global.ZEN_MODE, modeContentObserver);
+ globalSettings.registerContentObserverSync(Global.ZEN_MODE_CONFIG_ETAG,
configContentObserver);
}
updateZenMode(getModeSettingValueFromProvider());
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 7494649..4963aae 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -444,7 +444,7 @@
filter.addAction(Intent.ACTION_WALLPAPER_CHANGED);
mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, mMainExecutor,
UserHandle.ALL);
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
false,
new ContentObserver(mBgHandler) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
index ec89610..ed52233 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
@@ -37,7 +37,6 @@
interface SettingsProxy {
/** Returns the [ContentResolver] this instance was constructed with. */
fun getContentResolver(): ContentResolver
-
/**
* Construct the content URI for a particular name/value pair, useful for monitoring changes
* with a ContentObserver.
@@ -46,42 +45,36 @@
* @return the corresponding content URI, or null if not present
*/
fun getUriFor(name: String): Uri
-
/**
* Convenience wrapper around [ContentResolver.registerContentObserver].'
*
* Implicitly calls [getUriFor] on the passed in name.
*/
- fun registerContentObserver(name: String, settingsObserver: ContentObserver) {
- registerContentObserver(getUriFor(name), settingsObserver)
+ fun registerContentObserverSync(name: String, settingsObserver: ContentObserver) {
+ registerContentObserverSync(getUriFor(name), settingsObserver)
}
-
/** Convenience wrapper around [ContentResolver.registerContentObserver].' */
- fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) =
- registerContentObserver(uri, false, settingsObserver)
-
+ fun registerContentObserverSync(uri: Uri, settingsObserver: ContentObserver) =
+ registerContentObserverSync(uri, false, settingsObserver)
/**
* Convenience wrapper around [ContentResolver.registerContentObserver].'
*
* Implicitly calls [getUriFor] on the passed in name.
*/
- fun registerContentObserver(
+ fun registerContentObserverSync(
name: String,
notifyForDescendants: Boolean,
settingsObserver: ContentObserver
- ) = registerContentObserver(getUriFor(name), notifyForDescendants, settingsObserver)
-
+ ) = registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
/** Convenience wrapper around [ContentResolver.registerContentObserver].' */
- fun registerContentObserver(
+ fun registerContentObserverSync(
uri: Uri,
notifyForDescendants: Boolean,
settingsObserver: ContentObserver
) = getContentResolver().registerContentObserver(uri, notifyForDescendants, settingsObserver)
-
/** See [ContentResolver.unregisterContentObserver]. */
- fun unregisterContentObserver(settingsObserver: ContentObserver) =
+ fun unregisterContentObserverSync(settingsObserver: ContentObserver) =
getContentResolver().unregisterContentObserver(settingsObserver)
-
/**
* Look up a name in the database.
*
@@ -89,7 +82,6 @@
* @return the corresponding value, or null if not present
*/
fun getString(name: String): String
-
/**
* Store a name/value pair into the database.
*
@@ -98,7 +90,6 @@
* @return true if the value was set, false on database errors
*/
fun putString(name: String, value: String): Boolean
-
/**
* Store a name/value pair into the database.
*
@@ -129,7 +120,6 @@
* @see .resetToDefaults
*/
fun putString(name: String, value: String, tag: String, makeDefault: Boolean): Boolean
-
/**
* Convenience function for retrieving a single secure settings value as an integer. Note that
* internally setting values are always stored as strings; this function converts the string to
@@ -148,7 +138,6 @@
def
}
}
-
/**
* Convenience function for retrieving a single secure settings value as an integer. Note that
* internally setting values are always stored as strings; this function converts the string to
@@ -171,7 +160,6 @@
throw SettingNotFoundException(name)
}
}
-
/**
* Convenience function for updating a single settings value as an integer. This will either
* create a new entry in the table if the given name does not exist, or modify the value of the
@@ -185,7 +173,6 @@
fun putInt(name: String, value: Int): Boolean {
return putString(name, value.toString())
}
-
/**
* Convenience function for retrieving a single secure settings value as a boolean. Note that
* internally setting values are always stored as strings; this function converts the string to
@@ -199,7 +186,6 @@
fun getBool(name: String, def: Boolean): Boolean {
return getInt(name, if (def) 1 else 0) != 0
}
-
/**
* Convenience function for retrieving a single secure settings value as a boolean. Note that
* internally setting values are always stored as strings; this function converts the string to
@@ -217,7 +203,6 @@
fun getBool(name: String): Boolean {
return getInt(name) != 0
}
-
/**
* Convenience function for updating a single settings value as a boolean. This will either
* create a new entry in the table if the given name does not exist, or modify the value of the
@@ -231,7 +216,6 @@
fun putBool(name: String, value: Boolean): Boolean {
return putInt(name, if (value) 1 else 0)
}
-
/**
* Convenience function for retrieving a single secure settings value as a `long`. Note that
* internally setting values are always stored as strings; this function converts the string to
@@ -246,7 +230,6 @@
val valString = getString(name)
return parseLongOrUseDefault(valString, def)
}
-
/**
* Convenience function for retrieving a single secure settings value as a `long`. Note that
* internally setting values are always stored as strings; this function converts the string to
@@ -265,7 +248,6 @@
val valString = getString(name)
return parseLongOrThrow(name, valString)
}
-
/**
* Convenience function for updating a secure settings value as a long integer. This will either
* create a new entry in the table if the given name does not exist, or modify the value of the
@@ -279,7 +261,6 @@
fun putLong(name: String, value: Long): Boolean {
return putString(name, value.toString())
}
-
/**
* Convenience function for retrieving a single secure settings value as a floating point
* number. Note that internally setting values are always stored as strings; this function
@@ -294,7 +275,6 @@
val v = getString(name)
return parseFloat(v, def)
}
-
/**
* Convenience function for retrieving a single secure settings value as a float. Note that
* internally setting values are always stored as strings; this function converts the string to
@@ -313,7 +293,6 @@
val v = getString(name)
return parseFloatOrThrow(name, v)
}
-
/**
* Convenience function for updating a single settings value as a floating point number. This
* will either create a new entry in the table if the given name does not exist, or modify the
@@ -327,7 +306,6 @@
fun putFloat(name: String, value: Float): Boolean {
return putString(name, value.toString())
}
-
companion object {
/** Convert a string to a long, or uses a default if the string is malformed or null */
@JvmStatic
@@ -341,7 +319,6 @@
}
return value
}
-
/** Convert a string to a long, or throws an exception if the string is malformed or null */
@JvmStatic
@Throws(SettingNotFoundException::class)
@@ -355,7 +332,6 @@
throw SettingNotFoundException(name)
}
}
-
/** Convert a string to a float, or uses a default if the string is malformed or null */
@JvmStatic
fun parseFloat(v: String?, def: Float): Float {
@@ -365,7 +341,6 @@
def
}
}
-
/**
* Convert a string to a float, or throws an exception if the string is malformed or null
*/
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
index 7484368..d757e33 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
@@ -39,9 +39,9 @@
}
}
- names.forEach { name -> registerContentObserverForUser(name, observer, userId) }
+ names.forEach { name -> registerContentObserverForUserSync(name, observer, userId) }
- awaitClose { unregisterContentObserver(observer) }
+ awaitClose { unregisterContentObserverSync(observer) }
}
}
@@ -57,9 +57,9 @@
}
}
- names.forEach { name -> registerContentObserver(name, observer) }
+ names.forEach { name -> registerContentObserverSync(name, observer) }
- awaitClose { unregisterContentObserver(observer) }
+ awaitClose { unregisterContentObserverSync(observer) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
index 2285270..ed13943 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
@@ -41,10 +41,8 @@
* instances, unifying setting related actions in one place.
*/
interface UserSettingsProxy : SettingsProxy {
-
/** Returns that [UserTracker] this instance was constructed with. */
val userTracker: UserTracker
-
/** Returns the user id for the associated [ContentResolver]. */
var userId: Int
get() = getContentResolver().userId
@@ -53,7 +51,6 @@
"userId cannot be set in interface, use setter from an implementation instead."
)
}
-
/**
* Returns the actual current user handle when querying with the current user. Otherwise,
* returns the passed in user id.
@@ -63,63 +60,57 @@
userHandle
} else userTracker.userId
}
-
- override fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) {
- registerContentObserverForUser(uri, settingsObserver, userId)
+ override fun registerContentObserverSync(uri: Uri, settingsObserver: ContentObserver) {
+ registerContentObserverForUserSync(uri, settingsObserver, userId)
}
-
/** Convenience wrapper around [ContentResolver.registerContentObserver].' */
- override fun registerContentObserver(
+ override fun registerContentObserverSync(
uri: Uri,
notifyForDescendants: Boolean,
settingsObserver: ContentObserver
) {
- registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, userId)
+ registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId)
}
-
/**
* Convenience wrapper around [ContentResolver.registerContentObserver]
*
* Implicitly calls [getUriFor] on the passed in name.
*/
- fun registerContentObserverForUser(
+ fun registerContentObserverForUserSync(
name: String,
settingsObserver: ContentObserver,
userHandle: Int
) {
- registerContentObserverForUser(getUriFor(name), settingsObserver, userHandle)
+ registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle)
}
-
/** Convenience wrapper around [ContentResolver.registerContentObserver] */
- fun registerContentObserverForUser(
+ fun registerContentObserverForUserSync(
uri: Uri,
settingsObserver: ContentObserver,
userHandle: Int
) {
- registerContentObserverForUser(uri, false, settingsObserver, userHandle)
+ registerContentObserverForUserSync(uri, false, settingsObserver, userHandle)
}
-
/**
* Convenience wrapper around [ContentResolver.registerContentObserver]
*
* Implicitly calls [getUriFor] on the passed in name.
*/
- fun registerContentObserverForUser(
+ fun registerContentObserverForUserSync(
name: String,
notifyForDescendants: Boolean,
settingsObserver: ContentObserver,
userHandle: Int
) {
- registerContentObserverForUser(
+ registerContentObserverForUserSync(
getUriFor(name),
notifyForDescendants,
settingsObserver,
userHandle
)
}
-
/** Convenience wrapper around [ContentResolver.registerContentObserver] */
- fun registerContentObserverForUser(
+ fun registerContentObserverForUserSync(
uri: Uri,
notifyForDescendants: Boolean,
settingsObserver: ContentObserver,
@@ -136,7 +127,6 @@
Unit
}
}
-
/**
* Look up a name in the database.
*
@@ -146,10 +136,8 @@
override fun getString(name: String): String {
return getStringForUser(name, userId)
}
-
/** See [getString]. */
fun getStringForUser(name: String, userHandle: Int): String
-
/**
* Store a name/value pair into the database. Values written by this method will be overridden
* if a restore happens in the future.
@@ -162,10 +150,8 @@
override fun putString(name: String, value: String): Boolean {
return putStringForUser(name, value, userId)
}
-
/** Similar implementation to [putString] for the specified [userHandle]. */
fun putStringForUser(name: String, value: String, userHandle: Int): Boolean
-
/** Similar implementation to [putString] for the specified [userHandle]. */
fun putStringForUser(
name: String,
@@ -175,11 +161,9 @@
@UserIdInt userHandle: Int,
overrideableByRestore: Boolean
): Boolean
-
override fun getInt(name: String, def: Int): Int {
return getIntForUser(name, def, userId)
}
-
/** Similar implementation to [getInt] for the specified [userHandle]. */
fun getIntForUser(name: String, def: Int, userHandle: Int): Int {
val v = getStringForUser(name, userHandle)
@@ -189,10 +173,8 @@
def
}
}
-
@Throws(SettingNotFoundException::class)
override fun getInt(name: String) = getIntForUser(name, userId)
-
/** Similar implementation to [getInt] for the specified [userHandle]. */
@Throws(SettingNotFoundException::class)
fun getIntForUser(name: String, userHandle: Int): Int {
@@ -203,66 +185,52 @@
throw SettingNotFoundException(name)
}
}
-
override fun putInt(name: String, value: Int) = putIntForUser(name, value, userId)
-
/** Similar implementation to [getInt] for the specified [userHandle]. */
fun putIntForUser(name: String, value: Int, userHandle: Int) =
putStringForUser(name, value.toString(), userHandle)
-
override fun getBool(name: String, def: Boolean) = getBoolForUser(name, def, userId)
-
/** Similar implementation to [getBool] for the specified [userHandle]. */
fun getBoolForUser(name: String, def: Boolean, userHandle: Int) =
getIntForUser(name, if (def) 1 else 0, userHandle) != 0
-
@Throws(SettingNotFoundException::class)
override fun getBool(name: String) = getBoolForUser(name, userId)
-
/** Similar implementation to [getBool] for the specified [userHandle]. */
@Throws(SettingNotFoundException::class)
fun getBoolForUser(name: String, userHandle: Int): Boolean {
return getIntForUser(name, userHandle) != 0
}
-
override fun putBool(name: String, value: Boolean): Boolean {
return putBoolForUser(name, value, userId)
}
-
/** Similar implementation to [putBool] for the specified [userHandle]. */
fun putBoolForUser(name: String, value: Boolean, userHandle: Int) =
putIntForUser(name, if (value) 1 else 0, userHandle)
-
/** Similar implementation to [getLong] for the specified [userHandle]. */
fun getLongForUser(name: String, def: Long, userHandle: Int): Long {
val valString = getStringForUser(name, userHandle)
return parseLongOrUseDefault(valString, def)
}
-
/** Similar implementation to [getLong] for the specified [userHandle]. */
@Throws(SettingNotFoundException::class)
fun getLongForUser(name: String, userHandle: Int): Long {
val valString = getStringForUser(name, userHandle)
return parseLongOrThrow(name, valString)
}
-
/** Similar implementation to [putLong] for the specified [userHandle]. */
fun putLongForUser(name: String, value: Long, userHandle: Int) =
putStringForUser(name, value.toString(), userHandle)
-
/** Similar implementation to [getFloat] for the specified [userHandle]. */
fun getFloatForUser(name: String, def: Float, userHandle: Int): Float {
val v = getStringForUser(name, userHandle)
return parseFloat(v, def)
}
-
/** Similar implementation to [getFloat] for the specified [userHandle]. */
@Throws(SettingNotFoundException::class)
fun getFloatForUser(name: String, userHandle: Int): Float {
val v = getStringForUser(name, userHandle)
return parseFloatOrThrow(name, v)
}
-
/** Similar implementation to [putFloat] for the specified [userHandle]. */
fun putFloatForUser(name: String, value: Float, userHandle: Int) =
putStringForUser(name, value.toString(), userHandle)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
index e0f64b4..154737c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
@@ -17,15 +17,18 @@
package com.android.systemui.volume.domain.interactor
import android.bluetooth.BluetoothAdapter
+import android.content.Context
import android.media.AudioDeviceInfo
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.media.BluetoothMediaDevice
import com.android.settingslib.media.MediaDevice
import com.android.settingslib.media.MediaDevice.MediaDeviceType
+import com.android.settingslib.media.PhoneMediaDevice
import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.settingslib.volume.data.repository.AudioSharingRepository
import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.volume.domain.model.AudioOutputDevice
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
@@ -36,6 +39,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -47,6 +51,7 @@
class AudioOutputInteractor
@Inject
constructor(
+ @Application private val context: Context,
audioRepository: AudioRepository,
audioModeInteractor: AudioModeInteractor,
@VolumePanelScope scope: CoroutineScope,
@@ -58,7 +63,7 @@
audioSharingRepository: AudioSharingRepository,
) {
- val currentAudioDevice: Flow<AudioOutputDevice> =
+ val currentAudioDevice: StateFlow<AudioOutputDevice> =
audioModeInteractor.isOngoingCall
.flatMapLatest { isOngoingCall ->
if (isOngoingCall) {
@@ -102,7 +107,7 @@
)
}
return AudioOutputDevice.BuiltIn(
- name = productName.toString(),
+ name = PhoneMediaDevice.getMediaTransferThisDeviceName(context),
icon = deviceIconInteractor.loadIcon(type),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
index 199bc3b..333f4ad 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
@@ -21,7 +21,7 @@
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.media.dialog.MediaOutputDialogManager
-import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlaybackState
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaOutputComponentModel
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import javax.inject.Inject
@@ -29,14 +29,12 @@
@VolumePanelScope
class MediaOutputActionsInteractor
@Inject
-constructor(
- private val mediaOutputDialogManager: MediaOutputDialogManager,
-) {
+constructor(private val mediaOutputDialogManager: MediaOutputDialogManager) {
- fun onBarClick(sessionWithPlaybackState: SessionWithPlaybackState?, expandable: Expandable?) {
- if (sessionWithPlaybackState?.isPlaybackActive == true) {
+ fun onBarClick(model: MediaOutputComponentModel?, expandable: Expandable?) {
+ if (model is MediaOutputComponentModel.MediaSession) {
mediaOutputDialogManager.createAndShowWithController(
- sessionWithPlaybackState.session.packageName,
+ model.session.packageName,
false,
expandable?.dialogController()
)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
new file mode 100644
index 0000000..ed25129
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.panel.component.mediaoutput.domain.interactor
+
+import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.systemui.volume.domain.interactor.AudioOutputInteractor
+import com.android.systemui.volume.domain.model.AudioOutputDevice
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaOutputComponentModel
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlaybackState
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.Result
+import com.android.systemui.volume.panel.shared.model.filterData
+import com.android.systemui.volume.panel.shared.model.wrapInResult
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
+
+/** Gathers together a domain state for the Media Output Volume Panel component. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@VolumePanelScope
+class MediaOutputComponentInteractor
+@Inject
+constructor(
+ @VolumePanelScope private val coroutineScope: CoroutineScope,
+ private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
+ audioOutputInteractor: AudioOutputInteractor,
+ audioModeInteractor: AudioModeInteractor,
+ interactor: MediaOutputInteractor,
+) {
+
+ private val sessionWithPlaybackState: StateFlow<Result<SessionWithPlaybackState?>> =
+ interactor.defaultActiveMediaSession
+ .filterData()
+ .flatMapLatest { session ->
+ if (session == null) {
+ flowOf(null)
+ } else {
+ mediaDeviceSessionInteractor.playbackState(session).mapNotNull { playback ->
+ playback?.let { SessionWithPlaybackState(session, playback.isActive) }
+ }
+ }
+ }
+ .wrapInResult()
+ .stateIn(
+ coroutineScope,
+ SharingStarted.Eagerly,
+ Result.Loading(),
+ )
+
+ private val currentAudioDevice: Flow<AudioOutputDevice> =
+ audioOutputInteractor.currentAudioDevice.filter { it !is AudioOutputDevice.Unknown }
+
+ val mediaOutputModel: StateFlow<Result<MediaOutputComponentModel>> =
+ audioModeInteractor.isOngoingCall
+ .flatMapLatest { isOngoingCall ->
+ audioOutputInteractor.isInAudioSharing.flatMapLatest { isInAudioSharing ->
+ if (isOngoingCall) {
+ currentAudioDevice.map {
+ MediaOutputComponentModel.Calling(it, isInAudioSharing)
+ }
+ } else {
+ combine(sessionWithPlaybackState.filterData(), currentAudioDevice) {
+ sessionWithPlaybackState,
+ currentAudioDevice ->
+ if (sessionWithPlaybackState == null) {
+ MediaOutputComponentModel.Idle(currentAudioDevice, isInAudioSharing)
+ } else {
+ MediaOutputComponentModel.MediaSession(
+ sessionWithPlaybackState.session,
+ sessionWithPlaybackState.isPlaybackActive,
+ currentAudioDevice,
+ isInAudioSharing,
+ )
+ }
+ }
+ }
+ }
+ }
+ .wrapInResult()
+ .stateIn(coroutineScope, SharingStarted.Eagerly, Result.Loading())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaOutputComponentModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaOutputComponentModel.kt
new file mode 100644
index 0000000..220fb2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaOutputComponentModel.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.panel.component.mediaoutput.domain.model
+
+import com.android.systemui.volume.domain.model.AudioOutputDevice
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
+
+/** Models domain data for the Media Output Component */
+sealed interface MediaOutputComponentModel {
+
+ val device: AudioOutputDevice
+ val isInAudioSharing: Boolean
+
+ /** There is an ongoing call on the device. */
+ data class Calling(
+ override val device: AudioOutputDevice,
+ override val isInAudioSharing: Boolean,
+ ) : MediaOutputComponentModel
+
+ /** There is media playing on the device. */
+ data class MediaSession(
+ val session: MediaDeviceSession,
+ val isPlaybackActive: Boolean,
+ override val device: AudioOutputDevice,
+ override val isInAudioSharing: Boolean,
+ ) : MediaOutputComponentModel
+
+ /** There is nothing playing on the device. */
+ data class Idle(
+ override val device: AudioOutputDevice,
+ override val isInAudioSharing: Boolean,
+ ) : MediaOutputComponentModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 40b7977..36b42f2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -18,33 +18,24 @@
import android.content.Context
import com.android.internal.logging.UiEventLogger
-import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Color
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.res.R
-import com.android.systemui.volume.domain.interactor.AudioOutputInteractor
import com.android.systemui.volume.domain.model.AudioOutputDevice
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
-import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlaybackState
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputComponentInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaOutputComponentModel
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.shared.model.Result
import com.android.systemui.volume.panel.shared.model.filterData
-import com.android.systemui.volume.panel.shared.model.wrapInResult
import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.stateIn
/** Models the UI of the Media Output Volume Panel component. */
@@ -56,61 +47,40 @@
private val context: Context,
@VolumePanelScope private val coroutineScope: CoroutineScope,
private val actionsInteractor: MediaOutputActionsInteractor,
- private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
- private val audioOutputInteractor: AudioOutputInteractor,
- audioModeInteractor: AudioModeInteractor,
- interactor: MediaOutputInteractor,
+ private val mediaOutputComponentInteractor: MediaOutputComponentInteractor,
private val uiEventLogger: UiEventLogger,
) {
- private val sessionWithPlaybackState: StateFlow<Result<SessionWithPlaybackState?>> =
- interactor.defaultActiveMediaSession
- .filterData()
- .flatMapLatest { session ->
- if (session == null) {
- flowOf(null)
- } else {
- mediaDeviceSessionInteractor.playbackState(session).mapNotNull { playback ->
- playback?.let { SessionWithPlaybackState(session, playback.isActive) }
- }
- }
- }
- .wrapInResult()
- .stateIn(
- coroutineScope,
- SharingStarted.Eagerly,
- Result.Loading(),
- )
-
val connectedDeviceViewModel: StateFlow<ConnectedDeviceViewModel?> =
- combine(
- sessionWithPlaybackState.filterData(),
- audioModeInteractor.isOngoingCall,
- audioOutputInteractor.currentAudioDevice.filter {
- it !is AudioOutputDevice.Unknown
- },
- audioOutputInteractor.isInAudioSharing,
- ) { mediaDeviceSession, isOngoingCall, currentConnectedDevice, isInAudioSharing ->
+ mediaOutputComponentInteractor.mediaOutputModel
+ .filterData()
+ .map { mediaOutputModel ->
val label =
- when {
- isOngoingCall -> context.getString(R.string.media_output_title_ongoing_call)
- mediaDeviceSession?.isPlaybackActive == true ->
- context.getString(
- R.string.media_output_label_title,
- mediaDeviceSession.session.appLabel
- )
- else -> context.getString(R.string.media_output_title_without_playing)
+ when (mediaOutputModel) {
+ is MediaOutputComponentModel.Idle -> {
+ context.getString(R.string.media_output_title_without_playing)
+ }
+ is MediaOutputComponentModel.MediaSession -> {
+ if (mediaOutputModel.isPlaybackActive) {
+ context.getString(
+ R.string.media_output_label_title,
+ mediaOutputModel.session.appLabel,
+ )
+ } else {
+ context.getString(R.string.media_output_title_without_playing)
+ }
+ }
+ is MediaOutputComponentModel.Calling -> {
+ context.getString(R.string.media_output_title_ongoing_call)
+ }
}
ConnectedDeviceViewModel(
label,
- when (isInAudioSharing) {
- true -> {
- context.getString(R.string.audio_sharing_description)
- }
- false -> {
- currentConnectedDevice.name
- }
- }
+ if (mediaOutputModel.isInAudioSharing) {
+ context.getString(R.string.audio_sharing_description)
+ } else {
+ mediaOutputModel.device.name
+ },
)
}
.stateIn(
@@ -120,16 +90,20 @@
)
val deviceIconViewModel: StateFlow<DeviceIconViewModel?> =
- combine(sessionWithPlaybackState.filterData(), audioOutputInteractor.currentAudioDevice) {
- mediaDeviceSession,
- currentConnectedDevice ->
+ mediaOutputComponentInteractor.mediaOutputModel
+ .filterData()
+ .map { mediaOutputModel ->
val icon: Icon =
- currentConnectedDevice
- .takeIf { currentConnectedDevice !is AudioOutputDevice.Unknown }
+ mediaOutputModel.device
+ .takeIf { it !is AudioOutputDevice.Unknown }
?.icon
?.let { Icon.Loaded(it, null) }
?: Icon.Resource(R.drawable.ic_media_home_devices, null)
- if (mediaDeviceSession?.isPlaybackActive == true) {
+ val isPlaybackActive =
+ (mediaOutputModel as? MediaOutputComponentModel.MediaSession)
+ ?.isPlaybackActive == true
+ val isCalling = mediaOutputModel is MediaOutputComponentModel.Calling
+ if (isPlaybackActive || isCalling) {
DeviceIconViewModel.IsPlaying(
icon = icon,
iconColor =
@@ -156,8 +130,9 @@
)
val enabled: StateFlow<Boolean> =
- audioOutputInteractor.isInAudioSharing
- .map { !it }
+ mediaOutputComponentInteractor.mediaOutputModel
+ .filterData()
+ .map { !it.isInAudioSharing }
.stateIn(
coroutineScope,
SharingStarted.Eagerly,
@@ -166,7 +141,11 @@
fun onBarClick(expandable: Expandable?) {
uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_MEDIA_OUTPUT_CLICKED)
- val result = sessionWithPlaybackState.value
- actionsInteractor.onBarClick((result as? Result.Data)?.data, expandable)
+ val result: Result<MediaOutputComponentModel> =
+ mediaOutputComponentInteractor.mediaOutputModel.value
+ actionsInteractor.onBarClick(
+ (result as? Result.Data<MediaOutputComponentModel>)?.data,
+ expandable
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
index 4b2d26a..6c6a1cc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
@@ -48,15 +48,31 @@
private val uiEventLogger: UiEventLogger,
) {
+ private val spatialSpeakerIcon =
+ Icon.Resource(R.drawable.ic_spatial_speaker, contentDescription = null)
+
val spatialAudioButton: StateFlow<ButtonViewModel?> =
- interactor.isEnabled
- .map {
- val isChecked = it is SpatialAudioEnabledModel.SpatialAudioEnabled
- it.toViewModel(isChecked)
+ combine(interactor.isEnabled, interactor.isAvailable) { isEnabled, isAvailable ->
+ isEnabled
+ .toViewModel(
+ isChecked = isEnabled is SpatialAudioEnabledModel.SpatialAudioEnabled,
+ isHeadTrackingAvailable =
+ isAvailable is SpatialAudioAvailabilityModel.SpatialAudio,
+ )
.copy(label = context.getString(R.string.volume_panel_spatial_audio_title))
}
.stateIn(scope, SharingStarted.Eagerly, null)
+ val shouldUsePopup: StateFlow<Boolean> =
+ interactor.isAvailable
+ .map {
+ // head tracking availability means there are three possible states for the spatial
+ // audio: disabled, enabled regular, enabled with head tracking.
+ // Show popup in this case instead of a togglealbe button.
+ it is SpatialAudioAvailabilityModel.SpatialAudio
+ }
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
val isAvailable: StateFlow<Boolean> =
availabilityCriteria.isAvailable().stateIn(scope, SharingStarted.Eagerly, true)
@@ -73,8 +89,12 @@
}
}
.map { isEnabled ->
- val isChecked = isEnabled == currentIsEnabled
- val buttonViewModel: ButtonViewModel = isEnabled.toViewModel(isChecked)
+ val buttonViewModel: ButtonViewModel =
+ isEnabled.toViewModel(
+ isChecked = isEnabled == currentIsEnabled,
+ isHeadTrackingAvailable =
+ isAvailable is SpatialAudioAvailabilityModel.HeadTracking,
+ )
SpatialAudioButtonViewModel(button = buttonViewModel, model = isEnabled)
}
}
@@ -97,11 +117,21 @@
scope.launch { interactor.setEnabled(model) }
}
- private fun SpatialAudioEnabledModel.toViewModel(isChecked: Boolean): ButtonViewModel {
+ private fun SpatialAudioEnabledModel.toViewModel(
+ isChecked: Boolean,
+ isHeadTrackingAvailable: Boolean,
+ ): ButtonViewModel {
+ // This method deliberately uses the same icon for the case when head tracking is disabled
+ // to show a toggle button with a non-changing icon
if (this is SpatialAudioEnabledModel.HeadTrackingEnabled) {
return ButtonViewModel(
isActive = isChecked,
- icon = Icon.Resource(R.drawable.ic_head_tracking, contentDescription = null),
+ icon =
+ if (isHeadTrackingAvailable) {
+ Icon.Resource(R.drawable.ic_head_tracking, contentDescription = null)
+ } else {
+ spatialSpeakerIcon
+ },
label = context.getString(R.string.volume_panel_spatial_audio_tracking)
)
}
@@ -109,7 +139,12 @@
if (this is SpatialAudioEnabledModel.SpatialAudioEnabled) {
return ButtonViewModel(
isActive = isChecked,
- icon = Icon.Resource(R.drawable.ic_spatial_audio, contentDescription = null),
+ icon =
+ if (isHeadTrackingAvailable) {
+ Icon.Resource(R.drawable.ic_spatial_audio, contentDescription = null)
+ } else {
+ spatialSpeakerIcon
+ },
label = context.getString(R.string.volume_panel_spatial_audio_fixed)
)
}
@@ -117,7 +152,12 @@
if (this is SpatialAudioEnabledModel.Disabled) {
return ButtonViewModel(
isActive = isChecked,
- icon = Icon.Resource(R.drawable.ic_spatial_audio_off, contentDescription = null),
+ icon =
+ if (isHeadTrackingAvailable) {
+ Icon.Resource(R.drawable.ic_spatial_audio_off, contentDescription = null)
+ } else {
+ spatialSpeakerIcon
+ },
label = context.getString(R.string.volume_panel_spatial_audio_off)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
index ff18418..1d32a4f 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
@@ -149,12 +149,12 @@
if (event == WALLET_PREFERENCE_CHANGE && mWalletPreferenceObserver != null) {
mWalletPreferenceChangeEvents--;
if (mWalletPreferenceChangeEvents == 0) {
- mSecureSettings.unregisterContentObserver(mWalletPreferenceObserver);
+ mSecureSettings.unregisterContentObserverSync(mWalletPreferenceObserver);
}
} else if (event == DEFAULT_PAYMENT_APP_CHANGE && mDefaultPaymentAppObserver != null) {
mDefaultPaymentAppChangeEvents--;
if (mDefaultPaymentAppChangeEvents == 0) {
- mSecureSettings.unregisterContentObserver(mDefaultPaymentAppObserver);
+ mSecureSettings.unregisterContentObserverSync(mDefaultPaymentAppObserver);
}
} else if (event == DEFAULT_WALLET_APP_CHANGE && mDefaultWalletAppObserver != null) {
mDefaultWalletAppChangeEvents--;
@@ -312,7 +312,7 @@
}
};
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
false /* notifyForDescendants */,
mDefaultPaymentAppObserver,
@@ -351,7 +351,7 @@
}
};
- mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.registerContentObserverForUserSync(
QuickAccessWalletClientImpl.SETTING_KEY,
false /* notifyForDescendants */,
mWalletPreferenceObserver,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index 8c4179d..0a3225e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -161,8 +161,11 @@
doAnswer(this::checkMainThread).when(mKeyguardUpdateMonitor)
.removeCallback(any(KeyguardUpdateMonitorCallback.class));
- mCarrierTextCallbackInfo = new CarrierTextManager.CarrierTextCallbackInfo("",
- new CharSequence[]{}, false, new int[]{});
+ mCarrierTextCallbackInfo = new CarrierTextManager.CarrierTextCallbackInfo(
+ /* carrierText= */ "",
+ /* listOfCarriers= */ new CharSequence[]{},
+ /* anySimReady= */ false,
+ /* subscriptionIds= */ new int[]{});
when(mTelephonyManager.getSupportedModemCount()).thenReturn(3);
when(mTelephonyManager.getActiveModemCount()).thenReturn(3);
@@ -473,7 +476,7 @@
}
@Test
- public void carrierText_satelliteTextNull_notUsed() {
+ public void carrierText_satelliteTextNull_isSatelliteFalse_textNotUsed() {
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
list.add(TEST_SUBSCRIPTION);
@@ -491,12 +494,38 @@
CarrierTextManager.CarrierTextCallbackInfo.class);
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
- // THEN the default subscription carrier text is used
+ // THEN satellite mode is false and the default subscription carrier text is used
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ assertThat(captor.getValue().isInSatelliteMode).isFalse();
assertThat(captor.getValue().carrierText).isEqualTo(TEST_CARRIER);
}
@Test
+ public void carrierText_hasSatelliteText_isSatelliteTrue_textUsed() {
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION);
+ when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_READY);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ // WHEN the satellite text is non-null
+ mSatelliteViewModel.getCarrierText().setValue("Satellite Test Text");
+ mTestScope.getTestScheduler().runCurrent();
+
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+
+ // THEN satellite mode is true and the satellite text is used
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ assertThat(captor.getValue().isInSatelliteMode).isTrue();
+ assertThat(captor.getValue().carrierText).isEqualTo("Satellite Test Text");
+ }
+
+ @Test
public void carrierText_satelliteTextUpdates_autoTriggersCallback() {
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
@@ -517,6 +546,7 @@
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
// AND use the satellite text as the carrier text
+ assertThat(captor.getValue().isInSatelliteMode).isTrue();
assertThat(captor.getValue().carrierText).isEqualTo("Test satellite text");
// WHEN the satellite text is reset to null
@@ -528,6 +558,7 @@
// that doesn't include the satellite info
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ assertThat(captor.getValue().isInSatelliteMode).isFalse();
assertThat(captor.getValue().carrierText).isEqualTo(TEST_CARRIER);
}
@@ -566,10 +597,11 @@
mCarrierTextManager.setListening(mCarrierTextCallback);
// THEN we should automatically re-trigger #updateCarrierText and get callback info
- // that includes the new satellite text
+ // that includes the new satellite state and text
mTestScope.getTestScheduler().runCurrent();
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ assertThat(captor.getValue().isInSatelliteMode).isTrue();
assertThat(captor.getValue().carrierText).isEqualTo("New satellite text");
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 99b5a4b..cfa74f0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -181,7 +181,7 @@
ArgumentCaptor.forClass(ContentObserver.class);
mController.init();
mExecutor.runAllReady();
- verify(mSecureSettings).registerContentObserverForUser(
+ verify(mSecureSettings).registerContentObserverForUserSync(
eq(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK),
anyBoolean(), observerCaptor.capture(), eq(UserHandle.USER_ALL));
ContentObserver observer = observerCaptor.getValue();
@@ -247,7 +247,7 @@
ArgumentCaptor.forClass(ContentObserver.class);
mController.init();
mExecutor.runAllReady();
- verify(mSecureSettings).registerContentObserverForUser(
+ verify(mSecureSettings).registerContentObserverForUserSync(
eq(Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED), anyBoolean(),
observerCaptor.capture(), eq(UserHandle.USER_ALL));
ContentObserver observer = observerCaptor.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
index 92b06ba..138fed2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -393,7 +393,7 @@
mWindowMagnificationSettings.showSettingPanel();
- verify(mSecureSettings).registerContentObserverForUser(
+ verify(mSecureSettings).registerContentObserverForUserSync(
eq(ACCESSIBILITY_MAGNIFICATION_CAPABILITY),
any(ContentObserver.class),
eq(UserHandle.USER_CURRENT));
@@ -408,7 +408,7 @@
mWindowMagnificationSettings.showSettingPanel();
mWindowMagnificationSettings.hideSettingPanel();
- verify(mSecureSettings).unregisterContentObserver(any(ContentObserver.class));
+ verify(mSecureSettings).unregisterContentObserverSync(any(ContentObserver.class));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
index 9ba56d2..6391986 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
@@ -82,7 +82,12 @@
val keys =
captureMany<String> {
verify(secureSettings)
- .registerContentObserverForUser(capture(), anyBoolean(), any(), eq(USER_ID))
+ .registerContentObserverForUserSync(
+ capture(),
+ anyBoolean(),
+ any(),
+ eq(USER_ID)
+ )
}
assertThat(keys).containsExactly(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION)
@@ -102,7 +107,7 @@
val observer =
withArgCaptor<ContentObserver> {
verify(secureSettings)
- .registerContentObserverForUser(
+ .registerContentObserverForUserSync(
eq(setting),
anyBoolean(),
capture(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
index 590989d..154c373 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
@@ -332,7 +332,7 @@
}
private fun attachRepositoryToSettings() {
- secureSettings.registerContentObserver(
+ secureSettings.registerContentObserverSync(
SETTING_SHOW,
object : ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
@@ -343,7 +343,7 @@
}
)
- secureSettings.registerContentObserver(
+ secureSettings.registerContentObserverSync(
SETTING_ACTION,
object : ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt
new file mode 100644
index 0000000..3a86868
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.keyboard.docking.ui.viewmodel
+
+import android.graphics.Rect
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.view.WindowMetrics
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
+import com.android.systemui.keyboard.docking.domain.interactor.KeyboardDockingIndicationInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.spy
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyboardDockingIndicationViewModelTest : SysuiTestCase() {
+ private val testScope = TestScope(StandardTestDispatcher())
+
+ private lateinit var keyboardRepository: FakeKeyboardRepository
+ private lateinit var configurationRepository: FakeConfigurationRepository
+ private lateinit var underTest: KeyboardDockingIndicationViewModel
+ private lateinit var windowManager: WindowManager
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ keyboardRepository = FakeKeyboardRepository()
+ configurationRepository = FakeConfigurationRepository()
+ windowManager = spy(context.getSystemService(WindowManager::class.java)!!)
+
+ val keyboardDockingIndicationInteractor =
+ KeyboardDockingIndicationInteractor(keyboardRepository)
+ val configurationInteractor = ConfigurationInteractor(configurationRepository)
+
+ underTest =
+ KeyboardDockingIndicationViewModel(
+ windowManager,
+ context,
+ keyboardDockingIndicationInteractor,
+ configurationInteractor,
+ testScope.backgroundScope
+ )
+ }
+
+ @Test
+ fun onConfigurationChanged_createsNewConfig() {
+ val oldBounds = Rect(0, 0, 10, 10)
+ val newBounds = Rect(10, 10, 20, 20)
+ val inset = WindowInsets(Rect(1, 1, 1, 1))
+ val density = 1f
+
+ doReturn(WindowMetrics(oldBounds, inset, density))
+ .whenever(windowManager)
+ .currentWindowMetrics
+
+ val firstGlow = underTest.edgeGlow.value
+
+ testScope.runTest {
+ configurationRepository.onAnyConfigurationChange()
+ // Ensure there's some change in the config so that flow emits the new value.
+ doReturn(WindowMetrics(newBounds, inset, density))
+ .whenever(windowManager)
+ .currentWindowMetrics
+
+ val secondGlow = underTest.edgeGlow.value
+
+ assertThat(firstGlow.hashCode()).isNotEqualTo(secondGlow.hashCode())
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index 6f92733..144e634 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -1,20 +1,15 @@
package com.android.systemui.keyguard
import android.content.ComponentCallbacks2
-import android.graphics.HardwareRenderer
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.testDispatcher
@@ -26,7 +21,6 @@
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
import com.android.systemui.utils.GlobalWindowManager
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
@@ -62,52 +56,21 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, true)
keyguardRepository.setDozeAmount(0f)
keyguardRepository.setKeyguardGoingAway(false)
-
- val withDeps =
- KeyguardInteractorFactory.create(
- featureFlags = featureFlags,
- repository = keyguardRepository,
- )
- val keyguardInteractor = withDeps.keyguardInteractor
resourceTrimmer =
ResourceTrimmer(
- keyguardInteractor,
- powerInteractor = powerInteractor,
keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
globalWindowManager = globalWindowManager,
applicationScope = testScope.backgroundScope,
bgDispatcher = kosmos.testDispatcher,
- featureFlags = featureFlags,
sceneInteractor = kosmos.sceneInteractor,
)
resourceTrimmer.start()
}
@Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
- fun noChange_noOutputChanges() =
- testScope.runTest {
- testScope.runCurrent()
- verifyZeroInteractions(globalWindowManager)
- }
-
- @Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
- fun dozeAodDisabled_sleep_trimsMemory() =
- testScope.runTest {
- powerInteractor.setAsleepForTest()
- testScope.runCurrent()
- verify(globalWindowManager, times(1))
- .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
- verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL)
- }
-
- @Test
- @DisableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
- fun dozeAodDisabled_flagDisabled_sleep_doesntTrimMemory() =
+ fun dozeAodDisabled_sleep_doesntTrimMemory() =
testScope.runTest {
powerInteractor.setAsleepForTest()
testScope.runCurrent()
@@ -115,8 +78,7 @@
}
@Test
- @DisableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
- fun dozeEnabled_flagDisabled_sleepWithFullDozeAmount_doesntTrimMemory() =
+ fun dozeEnabled_sleepWithFullDozeAmount_doesntTrimMemory() =
testScope.runTest {
keyguardRepository.setDreaming(true)
keyguardRepository.setDozeAmount(1f)
@@ -126,20 +88,6 @@
}
@Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
- fun dozeEnabled_sleepWithFullDozeAmount_trimsMemory() =
- testScope.runTest {
- keyguardRepository.setDreaming(true)
- keyguardRepository.setDozeAmount(1f)
- powerInteractor.setAsleepForTest()
- testScope.runCurrent()
- verify(globalWindowManager, times(1))
- .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
- verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL)
- }
-
- @Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
fun dozeEnabled_sleepWithoutFullDozeAmount_doesntTrimMemory() =
testScope.runTest {
keyguardRepository.setDreaming(true)
@@ -150,34 +98,6 @@
}
@Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
- fun aodEnabled_sleepWithFullDozeAmount_trimsMemoryOnce() {
- testScope.runTest {
- keyguardRepository.setDreaming(true)
- keyguardRepository.setDozeAmount(0f)
- powerInteractor.setAsleepForTest()
-
- testScope.runCurrent()
- verifyZeroInteractions(globalWindowManager)
-
- generateSequence(0f) { it + 0.1f }
- .takeWhile { it < 1f }
- .forEach {
- keyguardRepository.setDozeAmount(it)
- testScope.runCurrent()
- }
- verifyZeroInteractions(globalWindowManager)
-
- keyguardRepository.setDozeAmount(1f)
- testScope.runCurrent()
- verify(globalWindowManager, times(1))
- .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
- verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL)
- }
- }
-
- @Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
fun aodEnabled_deviceWakesHalfWayThrough_doesNotTrimMemory() {
testScope.runTest {
keyguardRepository.setDreaming(true)
@@ -209,7 +129,6 @@
}
@Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
@DisableSceneContainer
fun keyguardTransitionsToGone_trimsFontCache() =
testScope.runTest {
@@ -220,12 +139,10 @@
)
verify(globalWindowManager, times(1))
.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
- verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_FONT)
verifyNoMoreInteractions(globalWindowManager)
}
@Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
@EnableSceneContainer
fun keyguardTransitionsToGone_trimsFontCache_scene_container() =
testScope.runTest {
@@ -233,38 +150,6 @@
verify(globalWindowManager, times(1))
.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
- verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_FONT)
verifyNoMoreInteractions(globalWindowManager)
}
-
- @Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
- @DisableSceneContainer
- fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache() =
- testScope.runTest {
- featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false)
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- testScope
- )
- // Memory hidden should still be called.
- verify(globalWindowManager, times(1))
- .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
- verify(globalWindowManager, times(0)).trimCaches(any())
- }
-
- @Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
- @EnableSceneContainer
- fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache_scene_container() =
- testScope.runTest {
- featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false)
- kosmos.setSceneTransition(Idle(Scenes.Gone))
-
- // Memory hidden should still be called.
- verify(globalWindowManager, times(1))
- .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
- verify(globalWindowManager, times(0)).trimCaches(any())
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 16dd9af..e33d75c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -50,7 +50,6 @@
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
-import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
@@ -290,7 +289,6 @@
underTest =
KeyguardQuickAffordancesCombinedViewModel(
- applicationScope = kosmos.applicationCoroutineScope,
quickAffordanceInteractor =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = keyguardInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index ad5d1ea..f7b3f2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -40,6 +40,7 @@
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.MediaTestUtils
@@ -158,6 +159,7 @@
testDispatcher = UnconfinedTestDispatcher()
mediaCarouselController =
MediaCarouselController(
+ applicationScope = kosmos.applicationCoroutineScope,
context = context,
mediaControlPanelFactory = mediaControlPanelFactory,
visualStabilityProvider = visualStabilityProvider,
@@ -194,7 +196,7 @@
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
MediaPlayerData.clear()
verify(globalSettings)
- .registerContentObserver(
+ .registerContentObserverSync(
eq(Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE)),
capture(settingsObserverCaptor)
)
@@ -893,7 +895,10 @@
mediaCarouselController.updateHostVisibility = { updatedVisibility = true }
mediaCarouselController.mediaCarousel = mediaCarousel
- val settingsJob = mediaCarouselController.listenForLockscreenSettingChanges(this)
+ val settingsJob =
+ mediaCarouselController.listenForLockscreenSettingChanges(
+ kosmos.applicationCoroutineScope
+ )
secureSettings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, false)
val keyguardJob = mediaCarouselController.listenForAnyStateToLockscreenTransition(this)
@@ -920,7 +925,10 @@
mediaCarouselController.updateHostVisibility = { updatedVisibility = true }
mediaCarouselController.mediaCarousel = mediaCarousel
- val settingsJob = mediaCarouselController.listenForLockscreenSettingChanges(this)
+ val settingsJob =
+ mediaCarouselController.listenForLockscreenSettingChanges(
+ kosmos.applicationCoroutineScope
+ )
secureSettings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, true)
val keyguardJob = mediaCarouselController.listenForAnyStateToLockscreenTransition(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index eb0a9c4..6d1bc82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -53,7 +53,6 @@
import com.android.systemui.animation.Expandable;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.nano.SystemUIProtoDump;
-import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.qs.QSFactory;
@@ -133,8 +132,6 @@
private SparseArray<SharedPreferences> mSharedPreferencesByUser;
- private FakeFeatureFlags mFeatureFlags;
-
private QSPipelineFlagsRepository mQSPipelineFlagsRepository;
private FakeExecutor mMainExecutor;
@@ -144,7 +141,6 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mFeatureFlags = new FakeFeatureFlags();
mSetFlagsRule.disableFlags(FLAG_QS_NEW_PIPELINE);
mSetFlagsRule.disableFlags(FLAG_QS_NEW_TILES);
@@ -170,12 +166,12 @@
saveSetting("");
setUpTileFactory();
mQSTileHost = new TestQSTileHost(mContext, () -> null, mDefaultFactory, mMainExecutor,
- mPluginManager, mTunerService, () -> mAutoTiles, mShadeController,
+ mPluginManager, mTunerService, () -> mAutoTiles, () -> mShadeController,
mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
mTileLifecycleManagerFactory, mUserFileManager, mQSPipelineFlagsRepository);
mMainExecutor.runAllReady();
- mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {
+ mSecureSettings.registerContentObserverForUserSync(SETTING, new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
@@ -689,7 +685,7 @@
QSFactory defaultFactory, Executor mainExecutor,
PluginManager pluginManager, TunerService tunerService,
Provider<AutoTileManager> autoTiles,
- ShadeController shadeController, QSLogger qsLogger,
+ Lazy<ShadeController> shadeController, QSLogger qsLogger,
UserTracker userTracker, SecureSettings secureSettings,
CustomTileStatePersister customTileStatePersister,
TileLifecycleManager.Factory tileLifecycleManagerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index 130aafb..6618308 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -19,6 +19,8 @@
import android.content.Context
import android.graphics.Rect
import android.graphics.drawable.Drawable
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.service.quicksettings.Tile
import android.testing.TestableLooper
import android.text.TextUtils
@@ -28,11 +30,13 @@
import android.widget.TextView
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS
import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.haptics.qs.QSLongPressEffect
import com.android.systemui.haptics.qs.qsLongPressEffect
import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.qsTileFactory
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -536,10 +540,30 @@
assertThat(tileView.haveLongPressPropertiesBeenReset).isTrue()
}
+ @Test
+ @EnableFlags(FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS)
+ fun onInit_withLongPressEffect_longPressEffectHasTileAndExpandable() {
+ val tile = kosmos.qsTileFactory.createTile("Test Tile")
+ tileView.init(tile)
+
+ assertThat(tileView.isTileAddedToLongPress).isTrue()
+ assertThat(tileView.isExpandableAddedToLongPress).isTrue()
+ }
+
+ @Test
+ @DisableFlags(FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS)
+ fun onInit_withoutLongPressEffect_longPressEffectDoesNotHaveTileAndExpandable() {
+ val tile = kosmos.qsTileFactory.createTile("Test Tile")
+ tileView.init(tile)
+
+ assertThat(tileView.isTileAddedToLongPress).isFalse()
+ assertThat(tileView.isExpandableAddedToLongPress).isFalse()
+ }
+
class FakeTileView(
context: Context,
collapsed: Boolean,
- longPressEffect: QSLongPressEffect?,
+ private val longPressEffect: QSLongPressEffect?,
) : QSTileViewImpl(
ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings),
collapsed,
@@ -547,6 +571,11 @@
) {
var constantLongPressEffectDuration = 500
+ val isTileAddedToLongPress: Boolean
+ get() = longPressEffect?.qsTile != null
+ val isExpandableAddedToLongPress: Boolean
+ get() = longPressEffect?.expandable != null
+
override fun getLongPressEffectDuration(): Int = constantLongPressEffectDuration
fun changeState(state: QSTile.State) {
handleStateChanged(state)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
index 5363c57..308b370 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
@@ -312,9 +312,10 @@
info = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{""},
- true,
+ /* anySimReady= */ true,
+ /* isInSatelliteMode= */ false,
new int[]{0},
- true /* airplaneMode */);
+ /* airplaneMode= */ true);
mCallback.updateCarrierInfo(info);
mTestableLooper.processAllMessages();
assertEquals(View.GONE, mShadeCarrierGroup.getNoSimTextView().getVisibility());
@@ -326,15 +327,59 @@
info = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{FIRST_CARRIER_NAME, ""},
- true,
+ /* anySimReady= */ true,
+ /* isInSatelliteMode= */ false,
new int[]{0, 1},
- false /* airplaneMode */);
+ /* airplaneMode= */ false);
mCallback.updateCarrierInfo(info);
mTestableLooper.processAllMessages();
assertEquals(View.VISIBLE, mShadeCarrierGroupController.getShadeCarrierVisibility(0));
}
@Test
+ public void isInSatelliteMode_true_noSimViewShownWithText() {
+ CarrierTextManager.CarrierTextCallbackInfo
+ info = new CarrierTextManager.CarrierTextCallbackInfo(
+ "Satellite Mode Test",
+ new CharSequence[]{FIRST_CARRIER_NAME},
+ /* anySimReady= */ true,
+ /* isInSatelliteMode= */ true,
+ new int[]{1},
+ /* airplaneMode= */ false);
+
+ mCallback.updateCarrierInfo(info);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mShadeCarrierGroup.getNoSimTextView().getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mShadeCarrierGroup.getNoSimTextView().getText()).isEqualTo(
+ "Satellite Mode Test");
+
+ verify(mShadeCarrier1).setVisibility(View.GONE);
+ verify(mShadeCarrier2).setVisibility(View.GONE);
+ verify(mShadeCarrier3).setVisibility(View.GONE);
+ }
+
+ @Test
+ public void isInSatelliteMode_false_normalSimViewsShown() {
+ CarrierTextManager.CarrierTextCallbackInfo
+ info = new CarrierTextManager.CarrierTextCallbackInfo(
+ "Satellite Mode Test",
+ new CharSequence[]{FIRST_CARRIER_NAME, SECOND_CARRIER_NAME},
+ /* anySimReady= */ true,
+ /* isInSatelliteMode= */ false,
+ new int[]{0, 1},
+ /* airplaneMode= */ false);
+
+ mCallback.updateCarrierInfo(info);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mShadeCarrierGroup.getNoSimTextView().getVisibility()).isEqualTo(View.GONE);
+
+ verify(mShadeCarrier1).setVisibility(View.VISIBLE);
+ verify(mShadeCarrier2).setVisibility(View.VISIBLE);
+ }
+
+ @Test
public void testListenerNotCalledOnRegistreation() {
mShadeCarrierGroupController
.setOnSingleCarrierChangedListener(mOnSingleCarrierChangedListener);
@@ -350,8 +395,7 @@
SINGLE_CARRIER_TEXT,
new CharSequence[]{SINGLE_CARRIER_TEXT},
true,
- new int[]{0},
- false /* airplaneMode */);
+ new int[]{0});
mCallback.updateCarrierInfo(info);
mTestableLooper.processAllMessages();
@@ -369,8 +413,7 @@
MULTI_CARRIER_TEXT,
new CharSequence[]{FIRST_CARRIER_NAME, SECOND_CARRIER_NAME},
true,
- new int[]{0, 1},
- false /* airplaneMode */);
+ new int[]{0, 1});
mCallback.updateCarrierInfo(info);
mTestableLooper.processAllMessages();
@@ -387,16 +430,14 @@
SINGLE_CARRIER_TEXT,
new CharSequence[]{FIRST_CARRIER_NAME},
true,
- new int[]{0},
- false /* airplaneMode */);
+ new int[]{0});
CarrierTextManager.CarrierTextCallbackInfo
multiCarrierInfo = new CarrierTextManager.CarrierTextCallbackInfo(
MULTI_CARRIER_TEXT,
new CharSequence[]{FIRST_CARRIER_NAME, SECOND_CARRIER_NAME},
true,
- new int[]{0, 1},
- false /* airplaneMode */);
+ new int[]{0, 1});
mCallback.updateCarrierInfo(singleCarrierInfo);
mTestableLooper.processAllMessages();
@@ -421,8 +462,7 @@
SINGLE_CARRIER_TEXT,
new CharSequence[]{FIRST_CARRIER_NAME},
true,
- new int[]{0},
- false /* airplaneMode */);
+ new int[]{0});
mCallback.updateCarrierInfo(singleCarrierInfo);
mTestableLooper.processAllMessages();
@@ -443,8 +483,7 @@
MULTI_CARRIER_TEXT,
new CharSequence[]{FIRST_CARRIER_NAME, SECOND_CARRIER_NAME},
true,
- new int[]{0, 1},
- false /* airplaneMode */);
+ new int[]{0, 1});
mCallback.updateCarrierInfo(multiCarrierInfo);
mTestableLooper.processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 63ce233..068e166 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -32,6 +32,7 @@
import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
import android.provider.Settings
+import android.testing.TestableLooper.RunWithLooper
import android.view.View
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
@@ -84,6 +85,7 @@
import java.util.concurrent.Executor
@SmallTest
+@RunWithLooper(setAsMainLooper = true)
class LockscreenSmartspaceControllerTest : SysuiTestCase() {
companion object {
const val SMARTSPACE_TIME_TOO_EARLY = 1000L
@@ -778,6 +780,7 @@
}
@Test
+ @RunWithLooper(setAsMainLooper = false)
fun testConnectAttemptBeforeInitializationShouldNotCreateSession() {
// GIVEN an uninitalized smartspaceView
// WHEN the device is provisioned
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index 8012768..dc9c22f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -320,7 +320,7 @@
.thenReturn(1);
ArgumentCaptor<ContentObserver> contentObserverCaptor = ArgumentCaptor.forClass(
ContentObserver.class);
- verify(mSecureSettings).registerContentObserverForUser(eq(SHOW_NOTIFICATION_SNOOZE),
+ verify(mSecureSettings).registerContentObserverForUserSync(eq(SHOW_NOTIFICATION_SNOOZE),
contentObserverCaptor.capture(), anyInt());
ContentObserver contentObserver = contentObserverCaptor.getValue();
contentObserver.onChange(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
index 7943872..2f77b33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
@@ -69,22 +69,21 @@
private val groupMembershipManager: GroupMembershipManager = mock()
private val section = NotifSection(mock(), 0)
- private val entry = NotificationEntryBuilder()
- .setSection(section)
- .setParent(GroupEntry.ROOT_ENTRY)
- .build()
+ private val entry =
+ NotificationEntryBuilder().setSection(section).setParent(GroupEntry.ROOT_ENTRY).build()
private lateinit var contentObserver: ContentObserver
- private val adjustmentProvider = NotifUiAdjustmentProvider(
- handler,
- secureSettings,
- lockscreenUserManager,
- sensitiveNotifProtectionController,
- sectionStyleProvider,
- userTracker,
- groupMembershipManager,
- )
+ private val adjustmentProvider =
+ NotifUiAdjustmentProvider(
+ handler,
+ secureSettings,
+ lockscreenUserManager,
+ sensitiveNotifProtectionController,
+ sectionStyleProvider,
+ userTracker,
+ groupMembershipManager,
+ )
@Before
fun setup() {
@@ -92,9 +91,8 @@
adjustmentProvider.addDirtyListener(dirtyListener)
verify(secureSettings).getIntForUser(eq(SHOW_NOTIFICATION_SNOOZE), any(), any())
contentObserver = withArgCaptor {
- verify(secureSettings).registerContentObserverForUser(
- eq(SHOW_NOTIFICATION_SNOOZE), capture(), any()
- )
+ verify(secureSettings)
+ .registerContentObserverForUserSync(eq(SHOW_NOTIFICATION_SNOOZE), capture(), any())
}
verifyNoMoreInteractions(secureSettings, dirtyListener)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 62bffdd..0d3ab86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -27,6 +27,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@@ -50,7 +51,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.filters.Suppress;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.media.controls.util.MediaFeatureFlag;
@@ -81,7 +81,6 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
@RunWithLooper(setAsMainLooper = true)
-@Suppress
public class NotificationContentInflaterTest extends SysuiTestCase {
private NotificationContentInflater mNotificationInflater;
@@ -128,7 +127,7 @@
TestableLooper.get(this));
ExpandableNotificationRow row = mHelper.createRow(mBuilder.build());
mRow = spy(row);
- when(mNotifLayoutInflaterFactoryProvider.provide(any(), any()))
+ when(mNotifLayoutInflaterFactoryProvider.provide(any(), anyInt()))
.thenReturn(mNotifLayoutInflaterFactory);
mNotificationInflater = new NotificationContentInflater(
@@ -282,6 +281,7 @@
}
@Test
+ @Ignore
public void testUsesSameViewWhenCachedPossibleToReuse() throws Exception {
// GIVEN a cached view.
RemoteViews contractedRemoteView = mBuilder.createContentView();
@@ -347,6 +347,7 @@
}
@Test
+ @Ignore
public void testNotificationViewHeightTooSmallFailsValidation() {
View view = mock(View.class);
when(view.getHeight())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
index 352b79f..310fa67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
@@ -102,6 +102,7 @@
verify(userTracker).addCallback(any(), any())
verify(dumpManager).registerNormalDumpable(anyString(), eq(controller))
}
+
@Test
fun updateContentObserverRegistration_onUserChange_noSettingsListeners() {
verify(userTracker).addCallback(capture(userTrackerCallbackCaptor), any())
@@ -112,10 +113,11 @@
userCallback.onUserChanged(userId, context)
// Validate: Nothing to do, since we aren't monitoring settings
- verify(secureSettings, never()).unregisterContentObserver(any())
+ verify(secureSettings, never()).unregisterContentObserverSync(any())
verify(secureSettings, never())
- .registerContentObserverForUser(any(Uri::class.java), anyBoolean(), any(), anyInt())
+ .registerContentObserverForUserSync(any(Uri::class.java), anyBoolean(), any(), anyInt())
}
+
@Test
fun updateContentObserverRegistration_onUserChange_withSettingsListeners() {
// When: someone is listening to a setting
@@ -129,9 +131,9 @@
userCallback.onUserChanged(userId, context)
// Validate: The tracker is unregistered and re-registered with the new user
- verify(secureSettings).unregisterContentObserver(any())
+ verify(secureSettings).unregisterContentObserverSync(any())
verify(secureSettings)
- .registerContentObserverForUser(eq(settingUri1), eq(false), any(), eq(userId))
+ .registerContentObserverForUserSync(eq(settingUri1), eq(false), any(), eq(userId))
}
@Test
@@ -140,7 +142,7 @@
verifyZeroInteractions(secureSettings)
testableLooper.processAllMessages()
verify(secureSettings)
- .registerContentObserverForUser(
+ .registerContentObserverForUserSync(
eq(settingUri1),
eq(false),
any(),
@@ -149,7 +151,7 @@
controller.addCallback(settingUri1, Mockito.mock(Listener::class.java))
verify(secureSettings)
- .registerContentObserverForUser(any(Uri::class.java), anyBoolean(), any(), anyInt())
+ .registerContentObserverForUserSync(any(Uri::class.java), anyBoolean(), any(), anyInt())
}
@Test
@@ -158,7 +160,7 @@
verifyZeroInteractions(secureSettings)
testableLooper.processAllMessages()
verify(secureSettings)
- .registerContentObserverForUser(
+ .registerContentObserverForUserSync(
eq(settingUri1),
eq(false),
any(),
@@ -170,7 +172,7 @@
verifyNoMoreInteractions(secureSettings)
testableLooper.processAllMessages()
verify(secureSettings)
- .registerContentObserverForUser(
+ .registerContentObserverForUserSync(
eq(settingUri2),
eq(false),
any(),
@@ -186,7 +188,7 @@
verifyZeroInteractions(secureSettings)
testableLooper.processAllMessages()
verify(secureSettings)
- .registerContentObserverForUser(
+ .registerContentObserverForUserSync(
eq(settingUri1),
eq(false),
any(),
@@ -198,18 +200,18 @@
verifyNoMoreInteractions(secureSettings)
testableLooper.processAllMessages()
verify(secureSettings)
- .registerContentObserverForUser(eq(settingUri2), anyBoolean(), any(), anyInt())
+ .registerContentObserverForUserSync(eq(settingUri2), anyBoolean(), any(), anyInt())
clearInvocations(secureSettings)
controller.removeCallback(settingUri2, listenerSetting2)
testableLooper.processAllMessages()
- verify(secureSettings, never()).unregisterContentObserver(any())
+ verify(secureSettings, never()).unregisterContentObserverSync(any())
clearInvocations(secureSettings)
controller.removeCallback(settingUri1, listenerSetting1)
verifyNoMoreInteractions(secureSettings)
testableLooper.processAllMessages()
- verify(secureSettings).unregisterContentObserver(any())
+ verify(secureSettings).unregisterContentObserverSync(any())
}
@Test
@@ -253,7 +255,7 @@
testableLooper.processAllMessages()
verify(secureSettings)
- .registerContentObserverForUser(
+ .registerContentObserverForUserSync(
any(Uri::class.java),
anyBoolean(),
capture(settingsObserverCaptor),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index cb9790b..7ea85a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -133,6 +133,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.res.R;
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
+import com.android.systemui.scene.domain.startable.ScrimStartable;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
@@ -254,7 +255,6 @@
@Mock private IDreamManager mDreamManager;
@Mock private LightRevealScrimViewModel mLightRevealScrimViewModel;
@Mock private LightRevealScrim mLightRevealScrim;
- @Mock private ScrimController mScrimController;
@Mock private DozeScrimController mDozeScrimController;
@Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
@Mock private BiometricUnlockController mBiometricUnlockController;
@@ -355,6 +355,7 @@
private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor =
mKosmos.getBrightnessMirrorShowingInteractor();
+ private ScrimController mScrimController;
@Before
public void setup() throws Exception {
@@ -472,6 +473,8 @@
when(mUserTracker.getUserHandle()).thenReturn(
UserHandle.of(ActivityManager.getCurrentUser()));
+ mScrimController = mKosmos.getScrimController();
+
createCentralSurfaces();
}
@@ -733,7 +736,7 @@
@Test
public void testFingerprintNotification_UpdatesScrims() {
mCentralSurfaces.notifyBiometricAuthModeChanged();
- verify(mScrimController).transitionTo(any(), any());
+ verify(mScrimController).legacyTransitionTo(any(), any());
}
@Test
@@ -742,7 +745,7 @@
when(mBiometricUnlockController.getMode())
.thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK);
mCentralSurfaces.updateScrimController();
- verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any());
+ verify(mScrimController).legacyTransitionTo(eq(ScrimState.UNLOCKED), any());
}
@Test
@@ -753,7 +756,7 @@
// Starting a pulse should change the scrim controller to the pulsing state
when(mCameraLauncher.isLaunchingAffordance()).thenReturn(true);
mCentralSurfaces.updateScrimController();
- verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any());
+ verify(mScrimController).legacyTransitionTo(eq(ScrimState.UNLOCKED), any());
}
@Test
@@ -789,7 +792,7 @@
// Starting a pulse should change the scrim controller to the pulsing state
when(mCameraLauncher.isLaunchingAffordance()).thenReturn(false);
mCentralSurfaces.updateScrimController();
- verify(mScrimController).transitionTo(eq(ScrimState.KEYGUARD));
+ verify(mScrimController).legacyTransitionTo(eq(ScrimState.KEYGUARD));
}
@Test
@@ -817,12 +820,12 @@
// Starting a pulse should change the scrim controller to the pulsing state
when(mDozeServiceHost.isPulsing()).thenReturn(true);
mCentralSurfaces.updateScrimController();
- verify(mScrimController).transitionTo(eq(ScrimState.PULSING), any());
+ verify(mScrimController).legacyTransitionTo(eq(ScrimState.PULSING), any());
// Ending a pulse should take it back to keyguard state
when(mDozeServiceHost.isPulsing()).thenReturn(false);
mCentralSurfaces.updateScrimController();
- verify(mScrimController).transitionTo(eq(ScrimState.KEYGUARD));
+ verify(mScrimController).legacyTransitionTo(eq(ScrimState.KEYGUARD));
}
@Test
@@ -833,7 +836,7 @@
mCentralSurfaces.updateScrimController();
- verify(mScrimController, times(2)).transitionTo(eq(ScrimState.AOD));
+ verify(mScrimController, times(2)).legacyTransitionTo(eq(ScrimState.AOD));
verify(mStatusBarKeyguardViewManager).onKeyguardFadedAway();
}
@@ -845,7 +848,7 @@
mCentralSurfaces.updateScrimController();
- verify(mScrimController).transitionTo(eq(ScrimState.AUTH_SCRIMMED_SHADE));
+ verify(mScrimController).legacyTransitionTo(eq(ScrimState.AUTH_SCRIMMED_SHADE));
verify(mStatusBarKeyguardViewManager).onKeyguardFadedAway();
}
@@ -861,7 +864,7 @@
mCentralSurfaces.updateScrimController();
- verify(mScrimController).transitionTo(eq(ScrimState.AUTH_SCRIMMED));
+ verify(mScrimController).legacyTransitionTo(eq(ScrimState.AUTH_SCRIMMED));
}
@Test
@@ -877,7 +880,7 @@
mCentralSurfaces.updateScrimController();
// Tests the safeguard to reset the scrimstate
- verify(mScrimController, never()).transitionTo(any());
+ verify(mScrimController, never()).legacyTransitionTo(any());
}
@Test
@@ -893,7 +896,7 @@
mCentralSurfaces.updateScrimController();
// Tests the safeguard to reset the scrimstate
- verify(mScrimController, never()).transitionTo(eq(ScrimState.KEYGUARD));
+ verify(mScrimController, never()).legacyTransitionTo(eq(ScrimState.KEYGUARD));
}
@Test
@@ -908,7 +911,7 @@
mCentralSurfaces.updateScrimController();
- verify(mScrimController).transitionTo(eq(ScrimState.AUTH_SCRIMMED_SHADE));
+ verify(mScrimController).legacyTransitionTo(eq(ScrimState.AUTH_SCRIMMED_SHADE));
}
@Test
@@ -920,7 +923,7 @@
mTestScope.getTestScheduler().runCurrent();
// ScrimState also transitions.
- verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB);
+ verify(mScrimController).legacyTransitionTo(ScrimState.GLANCEABLE_HUB);
// Transition away from the glanceable hub.
mKosmos.getCommunalRepository()
@@ -929,7 +932,7 @@
mTestScope.getTestScheduler().runCurrent();
// ScrimState goes back to UNLOCKED.
- verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any());
+ verify(mScrimController).legacyTransitionTo(eq(ScrimState.UNLOCKED), any());
}
@Test
@@ -945,7 +948,7 @@
mTestScope.getTestScheduler().runCurrent();
// ScrimState also transitions.
- verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+ verify(mScrimController).legacyTransitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
// Transition away from the glanceable hub.
mKosmos.getCommunalRepository()
@@ -954,7 +957,7 @@
mTestScope.getTestScheduler().runCurrent();
// ScrimState goes back to UNLOCKED.
- verify(mScrimController).transitionTo(eq(ScrimState.DREAMING));
+ verify(mScrimController).legacyTransitionTo(eq(ScrimState.DREAMING));
}
@Test
@@ -1164,6 +1167,8 @@
@EnableSceneContainer
public void brightnesShowingChanged_flagEnabled_ScrimControllerNotified() {
mCentralSurfaces.registerCallbacks();
+ final ScrimStartable scrimStartable = mKosmos.getScrimStartable();
+ scrimStartable.start();
mBrightnessMirrorShowingInteractor.setMirrorShowing(true);
mTestScope.getTestScheduler().runCurrent();
@@ -1173,7 +1178,7 @@
mTestScope.getTestScheduler().runCurrent();
ArgumentCaptor<ScrimState> captor = ArgumentCaptor.forClass(ScrimState.class);
// The default is to call the one with the callback argument
- verify(mScrimController, atLeastOnce()).transitionTo(captor.capture(), any());
+ verify(mScrimController, atLeastOnce()).transitionTo(captor.capture());
assertThat(captor.getValue()).isNotEqualTo(ScrimState.BRIGHTNESS_MIRROR);
}
@@ -1184,8 +1189,9 @@
mBrightnessMirrorShowingInteractor.setMirrorShowing(true);
mTestScope.getTestScheduler().runCurrent();
- verify(mScrimController, never()).transitionTo(ScrimState.BRIGHTNESS_MIRROR);
- verify(mScrimController, never()).transitionTo(eq(ScrimState.BRIGHTNESS_MIRROR), any());
+ verify(mScrimController, never()).legacyTransitionTo(ScrimState.BRIGHTNESS_MIRROR);
+ verify(mScrimController, never())
+ .legacyTransitionTo(eq(ScrimState.BRIGHTNESS_MIRROR), any());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 416a869..4590071 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -309,7 +309,7 @@
mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false);
mTestScope.getTestScheduler().runCurrent();
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
}
@@ -327,7 +327,7 @@
@Test
public void transitionToKeyguard() {
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
@@ -342,7 +342,7 @@
@Test
public void transitionToShadeLocked() {
- mScrimController.transitionTo(SHADE_LOCKED);
+ mScrimController.legacyTransitionTo(SHADE_LOCKED);
mScrimController.setQsPosition(1f, 0);
finishAnimationsImmediately();
@@ -360,7 +360,7 @@
@Test
public void transitionToShadeLocked_clippingQs() {
mScrimController.setClipsQsScrim(true);
- mScrimController.transitionTo(SHADE_LOCKED);
+ mScrimController.legacyTransitionTo(SHADE_LOCKED);
mScrimController.setQsPosition(1f, 0);
finishAnimationsImmediately();
@@ -377,7 +377,7 @@
@Test
public void transitionToOff() {
- mScrimController.transitionTo(ScrimState.OFF);
+ mScrimController.legacyTransitionTo(ScrimState.OFF);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
@@ -394,7 +394,7 @@
@Test
public void transitionToAod_withRegularWallpaper() {
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
@@ -414,7 +414,7 @@
mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
mTestScope.getTestScheduler().runCurrent();
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
@@ -423,7 +423,7 @@
assertEquals(0f, mScrimController.getState().getMaxLightRevealScrimAlpha(), 0f);
// Pulsing notification should conserve AOD wallpaper.
- mScrimController.transitionTo(ScrimState.PULSING);
+ mScrimController.legacyTransitionTo(ScrimState.PULSING);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
@@ -438,7 +438,7 @@
mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
mTestScope.getTestScheduler().runCurrent();
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
@@ -457,7 +457,7 @@
mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
mTestScope.getTestScheduler().runCurrent();
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
mScrimController.setHasBackdrop(true);
finishAnimationsImmediately();
@@ -476,7 +476,7 @@
@Test
public void transitionToAod_withFrontAlphaUpdates() {
// Assert that setting the AOD front scrim alpha doesn't take effect in a non-AOD state.
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
mScrimController.setAodFrontScrimAlpha(0.5f);
finishAnimationsImmediately();
@@ -485,7 +485,7 @@
mScrimBehind, SEMI_TRANSPARENT));
// ... but that it does take effect once we enter the AOD state.
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
mScrimInFront, SEMI_TRANSPARENT,
@@ -501,8 +501,8 @@
// ... and make sure we recall the previous front scrim alpha even if we transition away
// for a bit.
- mScrimController.transitionTo(ScrimState.UNLOCKED);
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
mScrimInFront, OPAQUE,
@@ -512,8 +512,8 @@
// ... and alpha updates should be completely ignored if always_on is off.
// Passing it forward would mess up the wake-up transition.
mAlwaysOnEnabled = false;
- mScrimController.transitionTo(ScrimState.UNLOCKED);
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
mScrimController.setAodFrontScrimAlpha(0.3f);
assertEquals(ScrimState.AOD.getFrontAlpha(), mScrimInFront.getViewAlpha(), 0.001f);
@@ -523,7 +523,7 @@
@Test
public void transitionToAod_afterDocked_ignoresAlwaysOnAndUpdatesFrontAlpha() {
// Assert that setting the AOD front scrim alpha doesn't take effect in a non-AOD state.
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
mScrimController.setAodFrontScrimAlpha(0.5f);
finishAnimationsImmediately();
@@ -533,7 +533,7 @@
// ... and doesn't take effect when disabled always_on
mAlwaysOnEnabled = false;
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
mScrimInFront, OPAQUE,
@@ -542,9 +542,9 @@
// ... but will take effect after docked
when(mDockManager.isDocked()).thenReturn(true);
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
mScrimController.setAodFrontScrimAlpha(0.5f);
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
@@ -572,14 +572,14 @@
mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false);
mTestScope.getTestScheduler().runCurrent();
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
mScrimBehind, TRANSPARENT));
assertEquals(1f, mScrimController.getState().getMaxLightRevealScrimAlpha(), 0f);
- mScrimController.transitionTo(ScrimState.PULSING);
+ mScrimController.legacyTransitionTo(ScrimState.PULSING);
finishAnimationsImmediately();
// Front scrim should be transparent, but tinted
// Back scrim should be semi-transparent so the user can see the wallpaper
@@ -617,7 +617,7 @@
@Test
public void transitionToKeyguardBouncer() {
- mScrimController.transitionTo(BOUNCER);
+ mScrimController.legacyTransitionTo(BOUNCER);
finishAnimationsImmediately();
// Front scrim should be transparent
// Back scrim should be visible and tinted to the surface color
@@ -638,7 +638,7 @@
@Test
public void lockscreenToHubTransition_setsBehindScrimAlpha() {
// Start on lockscreen.
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
// Behind scrim starts at default alpha.
@@ -684,7 +684,7 @@
@Test
public void hubToLockscreenTransition_setsViewAlpha() {
// Start on glanceable hub.
- mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+ mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB);
finishAnimationsImmediately();
// Behind scrim starts at 0 alpha.
@@ -731,7 +731,7 @@
public void transitionToHub() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
- mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+ mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB);
finishAnimationsImmediately();
// All scrims transparent on the hub.
@@ -743,7 +743,7 @@
@Test
public void openBouncerOnHub() {
- mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+ mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB);
// Open the bouncer.
mScrimController.setRawPanelExpansionFraction(0f);
@@ -760,7 +760,7 @@
// Bouncer is closed.
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
- mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+ mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB);
finishAnimationsImmediately();
// All scrims are transparent.
@@ -772,7 +772,7 @@
@Test
public void openShadeOnHub() {
- mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+ mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB);
// Open the shade.
mScrimController.setQsPosition(1f, 0);
@@ -802,7 +802,7 @@
public void transitionToHubOverDream() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
- mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+ mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
finishAnimationsImmediately();
// All scrims transparent on the hub.
@@ -814,7 +814,7 @@
@Test
public void openBouncerOnHubOverDream() {
- mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+ mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
// Open the bouncer.
mScrimController.setRawPanelExpansionFraction(0f);
@@ -831,7 +831,7 @@
// Bouncer is closed.
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
- mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+ mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
finishAnimationsImmediately();
// All scrims are transparent.
@@ -843,7 +843,7 @@
@Test
public void openShadeOnHubOverDream() {
- mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+ mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
// Open the shade.
mScrimController.setQsPosition(1f, 0);
@@ -880,7 +880,7 @@
@Test
public void onThemeChangeWhileClipQsScrim_bouncerBehindTint_remainsBlack() {
mScrimController.setClipsQsScrim(true);
- mScrimController.transitionTo(BOUNCER);
+ mScrimController.legacyTransitionTo(BOUNCER);
finishAnimationsImmediately();
assertEquals(BOUNCER.getBehindTint(), Color.BLACK);
@@ -892,7 +892,7 @@
@Test
public void transitionToKeyguardBouncer_clippingQs() {
mScrimController.setClipsQsScrim(true);
- mScrimController.transitionTo(BOUNCER);
+ mScrimController.legacyTransitionTo(BOUNCER);
finishAnimationsImmediately();
// Front scrim should be transparent
// Back scrim should be clipping QS
@@ -912,7 +912,7 @@
@Test
public void disableClipQsScrimWithoutStateTransition_updatesTintAndAlpha() {
mScrimController.setClipsQsScrim(true);
- mScrimController.transitionTo(BOUNCER);
+ mScrimController.legacyTransitionTo(BOUNCER);
mScrimController.setClipsQsScrim(false);
@@ -934,7 +934,7 @@
@Test
public void enableClipQsScrimWithoutStateTransition_updatesTintAndAlpha() {
mScrimController.setClipsQsScrim(false);
- mScrimController.transitionTo(BOUNCER);
+ mScrimController.legacyTransitionTo(BOUNCER);
mScrimController.setClipsQsScrim(true);
@@ -955,7 +955,7 @@
@Test
public void transitionToBouncer() {
- mScrimController.transitionTo(ScrimState.BOUNCER_SCRIMMED);
+ mScrimController.legacyTransitionTo(ScrimState.BOUNCER_SCRIMMED);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
mScrimInFront, OPAQUE,
@@ -970,7 +970,7 @@
public void transitionToUnlocked_clippedQs() {
mScrimController.setClipsQsScrim(true);
mScrimController.setRawPanelExpansionFraction(0f);
- mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
assertScrimTinted(Map.of(
@@ -1000,7 +1000,7 @@
public void transitionToUnlocked_nonClippedQs_followsLargeScreensInterpolator() {
mScrimController.setClipsQsScrim(false);
mScrimController.setRawPanelExpansionFraction(0f);
- mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
assertScrimTinted(Map.of(
@@ -1037,15 +1037,15 @@
@Test
public void scrimStateCallback() {
- mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
assertEquals(mScrimState, ScrimState.UNLOCKED);
- mScrimController.transitionTo(BOUNCER);
+ mScrimController.legacyTransitionTo(BOUNCER);
finishAnimationsImmediately();
assertEquals(mScrimState, BOUNCER);
- mScrimController.transitionTo(ScrimState.BOUNCER_SCRIMMED);
+ mScrimController.legacyTransitionTo(ScrimState.BOUNCER_SCRIMMED);
finishAnimationsImmediately();
assertEquals(mScrimState, ScrimState.BOUNCER_SCRIMMED);
}
@@ -1054,7 +1054,7 @@
public void panelExpansion() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setRawPanelExpansionFraction(0.5f);
- mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
reset(mScrimBehind);
@@ -1114,7 +1114,7 @@
public void panelExpansionAffectsAlpha() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setRawPanelExpansionFraction(0.5f);
- mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
final float scrimAlpha = mScrimBehind.getViewAlpha();
@@ -1135,10 +1135,10 @@
@Test
public void transitionToUnlockedFromOff() {
// Simulate unlock with fingerprint without AOD
- mScrimController.transitionTo(ScrimState.OFF);
+ mScrimController.legacyTransitionTo(ScrimState.OFF);
mScrimController.setRawPanelExpansionFraction(0f);
finishAnimationsImmediately();
- mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
@@ -1157,10 +1157,10 @@
@Test
public void transitionToUnlockedFromAod() {
// Simulate unlock with fingerprint
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
mScrimController.setRawPanelExpansionFraction(0f);
finishAnimationsImmediately();
- mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
@@ -1179,9 +1179,9 @@
@Test
public void scrimBlanksBeforeLeavingAod() {
// Simulate unlock with fingerprint
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
- mScrimController.transitionTo(ScrimState.UNLOCKED,
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED,
new ScrimController.Callback() {
@Override
public void onDisplayBlanked() {
@@ -1203,9 +1203,9 @@
public void scrimBlankCallbackWhenUnlockingFromPulse() {
boolean[] blanked = {false};
// Simulate unlock with fingerprint
- mScrimController.transitionTo(ScrimState.PULSING);
+ mScrimController.legacyTransitionTo(ScrimState.PULSING);
finishAnimationsImmediately();
- mScrimController.transitionTo(ScrimState.UNLOCKED,
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED,
new ScrimController.Callback() {
@Override
public void onDisplayBlanked() {
@@ -1251,16 +1251,16 @@
mScrimController.setHasBackdrop(false);
mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false);
mTestScope.getTestScheduler().runCurrent();
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
// WHEN Simulate unlock with fingerprint
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
// WHEN transitioning to UNLOCKED, onDisplayCallbackBlanked callback called to continue
// the transition but the scrim was not actually blanked
- mScrimController.transitionTo(ScrimState.UNLOCKED,
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED,
new ScrimController.Callback() {
@Override
public void onDisplayBlanked() {
@@ -1279,7 +1279,7 @@
public void testScrimCallback() {
int[] callOrder = {0, 0, 0};
int[] currentCall = {0};
- mScrimController.transitionTo(ScrimState.AOD, new ScrimController.Callback() {
+ mScrimController.legacyTransitionTo(ScrimState.AOD, new ScrimController.Callback() {
@Override
public void onStart() {
callOrder[0] = ++currentCall[0];
@@ -1310,19 +1310,19 @@
@Test
public void testScrimCallbackCancelled() {
boolean[] cancelledCalled = {false};
- mScrimController.transitionTo(ScrimState.AOD, new ScrimController.Callback() {
+ mScrimController.legacyTransitionTo(ScrimState.AOD, new ScrimController.Callback() {
@Override
public void onCancelled() {
cancelledCalled[0] = true;
}
});
- mScrimController.transitionTo(ScrimState.PULSING);
+ mScrimController.legacyTransitionTo(ScrimState.PULSING);
Assert.assertTrue("onCancelled should have been called", cancelledCalled[0]);
}
@Test
public void testHoldsWakeLock_whenAOD() {
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
verify(mWakeLock).acquire(anyString());
verify(mWakeLock, never()).release(anyString());
finishAnimationsImmediately();
@@ -1331,24 +1331,24 @@
@Test
public void testDoesNotHoldWakeLock_whenUnlocking() {
- mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
verifyZeroInteractions(mWakeLock);
}
@Test
public void testCallbackInvokedOnSameStateTransition() {
- mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
ScrimController.Callback callback = mock(ScrimController.Callback.class);
- mScrimController.transitionTo(ScrimState.UNLOCKED, callback);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED, callback);
verify(callback).onFinished();
}
@Test
public void testHoldsAodWallpaperAnimationLock() {
// Pre-conditions
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
reset(mWakeLock);
@@ -1362,7 +1362,7 @@
@Test
public void testHoldsPulsingWallpaperAnimationLock() {
// Pre-conditions
- mScrimController.transitionTo(ScrimState.PULSING);
+ mScrimController.legacyTransitionTo(ScrimState.PULSING);
finishAnimationsImmediately();
reset(mWakeLock);
@@ -1378,9 +1378,9 @@
mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
mTestScope.getTestScheduler().runCurrent();
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any());
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
verify(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class));
}
@@ -1391,22 +1391,22 @@
mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
mTestScope.getTestScheduler().runCurrent();
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any());
}
@Test
public void testConservesExpansionOpacityAfterTransition() {
- mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.setRawPanelExpansionFraction(0.5f);
finishAnimationsImmediately();
final float expandedAlpha = mScrimBehind.getViewAlpha();
- mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+ mScrimController.legacyTransitionTo(ScrimState.BRIGHTNESS_MIRROR);
finishAnimationsImmediately();
- mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
assertEquals("Scrim expansion opacity wasn't conserved when transitioning back",
@@ -1415,12 +1415,12 @@
@Test
public void testCancelsOldAnimationBeforeBlanking() {
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
// Consume whatever value we had before
mAnimatorListener.reset();
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
Assert.assertTrue("Animators not canceled", mAnimatorListener.getNumCancels() != 0);
}
@@ -1439,13 +1439,13 @@
mTestScope.getTestScheduler().runCurrent();
mScrimController.setKeyguardOccluded(true);
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
mScrimBehind, OPAQUE));
- mScrimController.transitionTo(ScrimState.PULSING);
+ mScrimController.legacyTransitionTo(ScrimState.PULSING);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
@@ -1457,7 +1457,7 @@
mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
mTestScope.getTestScheduler().runCurrent();
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
@@ -1478,7 +1478,7 @@
if (state == ScrimState.UNINITIALIZED) {
continue;
}
- mScrimController.transitionTo(state);
+ mScrimController.legacyTransitionTo(state);
finishAnimationsImmediately();
assertEquals("Should be clickable unless AOD or PULSING, was: " + state,
mScrimBehind.getViewAlpha() != 0 && !eatsTouches.contains(state),
@@ -1520,7 +1520,7 @@
@Test
public void testScrimsOpaque_whenShadeFullyExpanded() {
- mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.setRawPanelExpansionFraction(1);
// notifications scrim alpha change require calling setQsPosition
mScrimController.setQsPosition(0, 300);
@@ -1536,7 +1536,7 @@
public void testAuthScrim_setClipQSScrimTrue_notifScrimOpaque_whenShadeFullyExpanded() {
// GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen
// with the camera app occluding the keyguard)
- mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.setClipsQsScrim(true);
mScrimController.setRawPanelExpansionFraction(1);
// notifications scrim alpha change require calling setQsPosition
@@ -1544,7 +1544,7 @@
finishAnimationsImmediately();
// WHEN the user triggers the auth bouncer
- mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
+ mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
finishAnimationsImmediately();
assertEquals("Behind scrim should be opaque",
@@ -1564,7 +1564,7 @@
public void testAuthScrim_setClipQSScrimFalse_notifScrimOpaque_whenShadeFullyExpanded() {
// GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen
// with the camera app occluding the keyguard)
- mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.setClipsQsScrim(false);
mScrimController.setRawPanelExpansionFraction(1);
// notifications scrim alpha change require calling setQsPosition
@@ -1572,7 +1572,7 @@
finishAnimationsImmediately();
// WHEN the user triggers the auth bouncer
- mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
+ mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
finishAnimationsImmediately();
assertEquals("Behind scrim should be opaque",
@@ -1590,11 +1590,11 @@
@Test
public void testAuthScrimKeyguard() {
// GIVEN device is on the keyguard
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
// WHEN the user triggers the auth bouncer
- mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED);
+ mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED);
finishAnimationsImmediately();
// THEN the front scrim is updated and the KEYGUARD scrims are the same as the
@@ -1608,7 +1608,7 @@
@Test
public void testScrimsVisible_whenShadeVisible() {
mScrimController.setClipsQsScrim(true);
- mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.setRawPanelExpansionFraction(0.3f);
// notifications scrim alpha change require calling setQsPosition
mScrimController.setQsPosition(0, 300);
@@ -1643,7 +1643,7 @@
@Test
public void testScrimsVisible_whenShadeVisible_clippingQs() {
mScrimController.setClipsQsScrim(true);
- mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.setRawPanelExpansionFraction(0.3f);
// notifications scrim alpha change require calling setQsPosition
mScrimController.setQsPosition(0.5f, 300);
@@ -1657,7 +1657,7 @@
@Test
public void testScrimsVisible_whenShadeVisibleOnLockscreen() {
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
mScrimController.setQsPosition(0.25f, 300);
assertScrimAlpha(Map.of(
@@ -1668,7 +1668,7 @@
@Test
public void testNotificationScrimTransparent_whenOnLockscreen() {
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
// even if shade is not pulled down, panel has expansion of 1 on the lockscreen
mScrimController.setRawPanelExpansionFraction(1);
mScrimController.setQsPosition(0f, /*qs panel bottom*/ 0);
@@ -1681,7 +1681,7 @@
@Test
public void testNotificationScrimVisible_afterOpeningShadeFromLockscreen() {
mScrimController.setRawPanelExpansionFraction(1);
- mScrimController.transitionTo(SHADE_LOCKED);
+ mScrimController.legacyTransitionTo(SHADE_LOCKED);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
@@ -1695,7 +1695,7 @@
// clipping doesn't change tested logic but allows to assert scrims more in line with
// their expected large screen behaviour
mScrimController.setClipsQsScrim(false);
- mScrimController.transitionTo(SHADE_LOCKED);
+ mScrimController.legacyTransitionTo(SHADE_LOCKED);
mScrimController.setQsPosition(1f, 100 /* value doesn't matter */);
assertTintAfterExpansion(mScrimBehind, SHADE_LOCKED.getBehindTint(), /* expansion= */ 1f);
@@ -1709,7 +1709,7 @@
public void expansionNotificationAlpha_shadeLocked_bouncerActive_usesBouncerInterpolator() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
- mScrimController.transitionTo(SHADE_LOCKED);
+ mScrimController.legacyTransitionTo(SHADE_LOCKED);
float expansion = 0.8f;
float expectedAlpha =
@@ -1725,7 +1725,7 @@
public void expansionNotificationAlpha_shadeLocked_bouncerNotActive_usesShadeInterpolator() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
- mScrimController.transitionTo(SHADE_LOCKED);
+ mScrimController.legacyTransitionTo(SHADE_LOCKED);
float expansion = 0.8f;
float expectedAlpha = ShadeInterpolation.getNotificationScrimAlpha(expansion);
@@ -1740,7 +1740,7 @@
public void notificationAlpha_unnocclusionAnimating_bouncerNotActive_usesKeyguardNotifAlpha() {
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
assertAlphaAfterExpansion(
mNotificationsScrim, ScrimState.KEYGUARD.getNotifAlpha(), /* expansion */ 0f);
@@ -1760,7 +1760,7 @@
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
mScrimController.setClipsQsScrim(true);
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
float expansion = 0.8f;
float alpha = 1 - BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansion);
@@ -1780,7 +1780,7 @@
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
mScrimController.setClipsQsScrim(true);
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
float expansion = 0.8f;
float alpha = 1 - ShadeInterpolation.getNotificationScrimAlpha(expansion);
@@ -1800,7 +1800,7 @@
when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
mScrimController.setClipsQsScrim(false);
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
assertThat(mScrimBehind.getTint())
.isEqualTo(ScrimState.KEYGUARD.getBehindTint());
@@ -1810,7 +1810,7 @@
public void testNotificationTransparency_followsTransitionToFullShade() {
mScrimController.setClipsQsScrim(true);
- mScrimController.transitionTo(SHADE_LOCKED);
+ mScrimController.legacyTransitionTo(SHADE_LOCKED);
mScrimController.setRawPanelExpansionFraction(1.0f);
finishAnimationsImmediately();
@@ -1821,7 +1821,7 @@
));
float shadeLockedAlpha = mNotificationsScrim.getViewAlpha();
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
mScrimController.setRawPanelExpansionFraction(1.0f);
finishAnimationsImmediately();
float keyguardAlpha = mNotificationsScrim.getViewAlpha();
@@ -1849,10 +1849,10 @@
@Test
public void notificationTransparency_followsNotificationScrimProgress() {
- mScrimController.transitionTo(SHADE_LOCKED);
+ mScrimController.legacyTransitionTo(SHADE_LOCKED);
mScrimController.setRawPanelExpansionFraction(1.0f);
finishAnimationsImmediately();
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
mScrimController.setRawPanelExpansionFraction(1.0f);
finishAnimationsImmediately();
@@ -1866,7 +1866,7 @@
@Test
public void notificationAlpha_qsNotClipped_alphaMatchesNotificationExpansionProgress() {
mScrimController.setClipsQsScrim(false);
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
// RawPanelExpansion and QsExpansion are usually used for the notification alpha
// calculation.
// Here we set them to non-zero values explicitly to make sure that in not clipped mode,
@@ -1927,7 +1927,7 @@
@Test
public void notificationBoundsTopGetsPassedToKeyguard() {
- mScrimController.transitionTo(SHADE_LOCKED);
+ mScrimController.legacyTransitionTo(SHADE_LOCKED);
mScrimController.setQsPosition(1f, 0);
finishAnimationsImmediately();
@@ -1938,7 +1938,7 @@
@Test
public void notificationBoundsTopDoesNotGetPassedToKeyguardWhenNotifScrimIsNotVisible() {
mScrimController.setKeyguardOccluded(true);
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
mScrimController.setNotificationsBounds(0f, 100f, 0f, 0f);
@@ -1949,7 +1949,7 @@
public void transitionToDreaming() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
- mScrimController.transitionTo(ScrimState.DREAMING);
+ mScrimController.legacyTransitionTo(ScrimState.DREAMING);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
@@ -1975,7 +1975,7 @@
@Test
public void setUnOccludingAnimationKeyguard() {
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
assertThat(mNotificationsScrim.getViewAlpha())
.isWithin(0.01f).of(ScrimState.KEYGUARD.getNotifAlpha());
@@ -1990,14 +1990,14 @@
@Test
public void testHidesScrimFlickerInActivity() {
mScrimController.setKeyguardOccluded(true);
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
mScrimBehind, TRANSPARENT,
mNotificationsScrim, TRANSPARENT));
- mScrimController.transitionTo(SHADE_LOCKED);
+ mScrimController.legacyTransitionTo(SHADE_LOCKED);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
@@ -2008,7 +2008,7 @@
@Test
public void notificationAlpha_inKeyguardState_bouncerNotActive_clipsQsScrimFalse() {
mScrimController.setClipsQsScrim(false);
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
float expansion = 0.8f;
assertAlphaAfterExpansion(mNotificationsScrim, 0f, expansion);
@@ -2016,14 +2016,14 @@
@Test
public void aodStateSetsFrontScrimToNotBlend() {
- mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.legacyTransitionTo(ScrimState.AOD);
assertFalse("Front scrim should not blend with main color",
mScrimInFront.shouldBlendWithMainColor());
}
@Test
public void applyState_unlocked_bouncerShowing() {
- mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.setBouncerHiddenFraction(0.99f);
mScrimController.setRawPanelExpansionFraction(0f);
finishAnimationsImmediately();
@@ -2032,13 +2032,13 @@
@Test
public void ignoreTransitionRequestWhileKeyguardTransitionRunning() {
- mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.mBouncerToGoneTransition.accept(
new TransitionStep(KeyguardState.PRIMARY_BOUNCER, KeyguardState.GONE, 0f,
TransitionState.RUNNING, "ScrimControllerTest"));
// This request should not happen
- mScrimController.transitionTo(ScrimState.BOUNCER);
+ mScrimController.legacyTransitionTo(ScrimState.BOUNCER);
assertThat(mScrimController.getState()).isEqualTo(ScrimState.UNLOCKED);
}
@@ -2055,16 +2055,16 @@
@Test
public void testDoNotAnimateChangeIfOccludeAnimationPlaying() {
mScrimController.setOccludeAnimationPlaying(true);
- mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
assertFalse(ScrimState.UNLOCKED.mAnimateChange);
}
@Test
public void testNotifScrimAlpha_1f_afterUnlockFinishedAndExpanded() {
- mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
when(mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()).thenReturn(true);
- mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
mScrimController.onUnlockAnimationFinished();
assertAlphaAfterExpansion(mNotificationsScrim, 1f, 1f);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index eb2538e..7d586cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -131,8 +131,9 @@
mobileMappings,
fakeBroadcastDispatcher,
context,
- IMMEDIATE,
+ /* bgDispatcher = */ IMMEDIATE,
scope,
+ /* mainDispatcher = */ IMMEDIATE,
FakeAirplaneModeRepository(),
wifiRepository,
mock(),
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 96e599f..76982ae 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
@@ -141,8 +141,8 @@
private val wifiPickerTrackerCallback =
argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>()
- private val dispatcher = StandardTestDispatcher()
- private val testScope = TestScope(dispatcher)
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
private lateinit var underTest: MobileConnectionsRepositoryImpl
@@ -194,7 +194,7 @@
flags,
testScope.backgroundScope,
mainExecutor,
- dispatcher,
+ testDispatcher,
wifiPickerTrackerFactory,
wifiManager,
wifiLogBuffer,
@@ -216,7 +216,7 @@
fakeBroadcastDispatcher,
connectivityManager,
telephonyManager = telephonyManager,
- bgDispatcher = dispatcher,
+ bgDispatcher = testDispatcher,
logger = logger,
mobileMappingsProxy = mobileMappings,
scope = testScope.backgroundScope,
@@ -249,8 +249,9 @@
mobileMappings,
fakeBroadcastDispatcher,
context,
- dispatcher,
+ /* bgDispatcher = */ testDispatcher,
testScope.backgroundScope,
+ /* mainDispatcher = */ testDispatcher,
airplaneModeRepository,
wifiRepository,
fullConnectionFactory,
@@ -1225,8 +1226,9 @@
mobileMappings,
fakeBroadcastDispatcher,
context,
- dispatcher,
+ testDispatcher,
testScope.backgroundScope,
+ testDispatcher,
airplaneModeRepository,
wifiRepository,
fullConnectionFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt
new file mode 100644
index 0000000..16132ba
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.surfaceeffects.glowboxeffect
+
+import android.graphics.Color
+import android.graphics.Paint
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.surfaceeffects.PaintDrawCallback
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class GlowBoxEffectTest : SysuiTestCase() {
+
+ @get:Rule val animatorTestRule = AnimatorTestRule(this)
+ private lateinit var config: GlowBoxConfig
+ private lateinit var glowBoxEffect: GlowBoxEffect
+ private lateinit var drawCallback: PaintDrawCallback
+
+ @Before
+ fun setup() {
+ drawCallback =
+ object : PaintDrawCallback {
+ override fun onDraw(paint: Paint) {}
+ }
+ config =
+ GlowBoxConfig(
+ startCenterX = 0f,
+ startCenterY = 0f,
+ endCenterX = 0f,
+ endCenterY = 0f,
+ width = 1f,
+ height = 1f,
+ color = Color.WHITE,
+ blurAmount = 0.1f,
+ duration = 100L,
+ easeInDuration = 100L,
+ easeOutDuration = 100L
+ )
+ glowBoxEffect = GlowBoxEffect(config, drawCallback)
+ }
+
+ @Test
+ fun play_paintCallback_triggersDrawCallback() {
+ var paintFromCallback: Paint? = null
+ drawCallback =
+ object : PaintDrawCallback {
+ override fun onDraw(paint: Paint) {
+ paintFromCallback = paint
+ }
+ }
+ glowBoxEffect = GlowBoxEffect(config, drawCallback)
+
+ assertThat(paintFromCallback).isNull()
+
+ glowBoxEffect.play()
+ animatorTestRule.advanceTimeBy(50L)
+
+ assertThat(paintFromCallback).isNotNull()
+ }
+
+ @Test
+ fun play_followsAnimationStateInOrder() {
+ assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.NOT_PLAYING)
+
+ glowBoxEffect.play()
+
+ assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.EASE_IN)
+
+ animatorTestRule.advanceTimeBy(config.easeInDuration + 50L)
+
+ assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.MAIN)
+
+ animatorTestRule.advanceTimeBy(config.duration + 50L)
+
+ assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.EASE_OUT)
+
+ animatorTestRule.advanceTimeBy(config.easeOutDuration + 50L)
+
+ assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.NOT_PLAYING)
+ }
+
+ @Test
+ fun finish_statePlaying_finishesAnimation() {
+ assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.NOT_PLAYING)
+
+ glowBoxEffect.play()
+ glowBoxEffect.finish()
+
+ assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.EASE_OUT)
+ }
+
+ @Test
+ fun finish_stateNotPlaying_doesNotFinishAnimation() {
+ assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.NOT_PLAYING)
+
+ glowBoxEffect.finish()
+
+ assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.NOT_PLAYING)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
index 6f58941..41d7fd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
@@ -21,8 +21,8 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.AnimatorTestRule
-import com.android.systemui.model.SysUiStateTest
import com.android.systemui.surfaceeffects.PaintDrawCallback
import com.android.systemui.surfaceeffects.RenderEffectDrawCallback
import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig
@@ -35,7 +35,7 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class LoadingEffectTest : SysUiStateTest() {
+class LoadingEffectTest : SysuiTestCase() {
@get:Rule val animatorTestRule = AnimatorTestRule(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 83ac3cc..53e033e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -202,7 +202,7 @@
verify(mWakefulnessLifecycle).addObserver(mWakefulnessLifecycleObserver.capture());
verify(mDumpManager).registerDumpable(any(), any());
verify(mDeviceProvisionedController).addCallback(mDeviceProvisionedListener.capture());
- verify(mSecureSettings).registerContentObserverForUser(
+ verify(mSecureSettings).registerContentObserverForUserSync(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES),
eq(false), mSettingsObserver.capture(), eq(UserHandle.USER_ALL)
);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
index 99f6303..25a44e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
@@ -83,7 +83,7 @@
@Test
public void testRegisterContentObserver() {
- mFakeSettings.registerContentObserver("cat", mContentObserver);
+ mFakeSettings.registerContentObserverSync("cat", mContentObserver);
mFakeSettings.putString("cat", "hat");
@@ -93,7 +93,7 @@
@Test
public void testRegisterContentObserverAllUsers() {
- mFakeSettings.registerContentObserverForUser(
+ mFakeSettings.registerContentObserverForUserSync(
mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL);
mFakeSettings.putString("cat", "hat");
@@ -104,8 +104,8 @@
@Test
public void testUnregisterContentObserver() {
- mFakeSettings.registerContentObserver("cat", mContentObserver);
- mFakeSettings.unregisterContentObserver(mContentObserver);
+ mFakeSettings.registerContentObserverSync("cat", mContentObserver);
+ mFakeSettings.unregisterContentObserverSync(mContentObserver);
mFakeSettings.putString("cat", "hat");
@@ -115,9 +115,9 @@
@Test
public void testUnregisterContentObserverAllUsers() {
- mFakeSettings.registerContentObserverForUser(
+ mFakeSettings.registerContentObserverForUserSync(
mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL);
- mFakeSettings.unregisterContentObserver(mContentObserver);
+ mFakeSettings.unregisterContentObserverSync(mContentObserver);
mFakeSettings.putString("cat", "hat");
@@ -128,7 +128,7 @@
@Test
public void testContentObserverDispatchCorrectUser() {
int user = 10;
- mFakeSettings.registerContentObserverForUser(
+ mFakeSettings.registerContentObserverForUserSync(
mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL
);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
index ab95707..eb11e38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
@@ -52,14 +52,14 @@
@Test
fun registerContentObserver_inputString_success() {
- mSettings.registerContentObserver(TEST_SETTING, mContentObserver)
+ mSettings.registerContentObserverSync(TEST_SETTING, mContentObserver)
verify(mSettings.getContentResolver())
.registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
}
@Test
fun registerContentObserver_inputString_notifyForDescendants_true() {
- mSettings.registerContentObserver(
+ mSettings.registerContentObserverSync(
TEST_SETTING,
notifyForDescendants = true,
mContentObserver
@@ -70,14 +70,14 @@
@Test
fun registerContentObserver_inputUri_success() {
- mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver)
+ mSettings.registerContentObserverSync(TEST_SETTING_URI, mContentObserver)
verify(mSettings.getContentResolver())
.registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
}
@Test
fun registerContentObserver_inputUri_notifyForDescendants_true() {
- mSettings.registerContentObserver(
+ mSettings.registerContentObserverSync(
TEST_SETTING_URI,
notifyForDescendants = true,
mContentObserver
@@ -88,7 +88,7 @@
@Test
fun unregisterContentObserver() {
- mSettings.unregisterContentObserver(mContentObserver)
+ mSettings.unregisterContentObserverSync(mContentObserver)
verify(mSettings.getContentResolver()).unregisterContentObserver(eq(mContentObserver))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
index 56328b9..38469ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
@@ -58,7 +58,7 @@
@Test
fun registerContentObserverForUser_inputString_success() {
- mSettings.registerContentObserverForUser(
+ mSettings.registerContentObserverForUserSync(
TEST_SETTING,
mContentObserver,
mUserTracker.userId
@@ -74,7 +74,7 @@
@Test
fun registerContentObserverForUser_inputString_notifyForDescendants_true() {
- mSettings.registerContentObserverForUser(
+ mSettings.registerContentObserverForUserSync(
TEST_SETTING,
notifyForDescendants = true,
mContentObserver,
@@ -91,7 +91,7 @@
@Test
fun registerContentObserverForUser_inputUri_success() {
- mSettings.registerContentObserverForUser(
+ mSettings.registerContentObserverForUserSync(
TEST_SETTING_URI,
mContentObserver,
mUserTracker.userId
@@ -107,7 +107,7 @@
@Test
fun registerContentObserverForUser_inputUri_notifyForDescendants_true() {
- mSettings.registerContentObserverForUser(
+ mSettings.registerContentObserverForUserSync(
TEST_SETTING_URI,
notifyForDescendants = true,
mContentObserver,
@@ -124,14 +124,14 @@
@Test
fun registerContentObserver_inputUri_success() {
- mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver)
+ mSettings.registerContentObserverSync(TEST_SETTING_URI, mContentObserver)
verify(mSettings.getContentResolver())
.registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), eq(0))
}
@Test
fun registerContentObserver_inputUri_notifyForDescendants_true() {
- mSettings.registerContentObserver(
+ mSettings.registerContentObserverSync(
TEST_SETTING_URI,
notifyForDescendants = true,
mContentObserver
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt
new file mode 100644
index 0000000..d208465
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.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.brightness.ui.viewmodel
+
+import com.android.systemui.brightness.domain.interactor.brightnessPolicyEnforcementInteractor
+import com.android.systemui.brightness.domain.interactor.screenBrightnessInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+
+val Kosmos.brightnessSliderViewModel: BrightnessSliderViewModel by
+ Kosmos.Fixture {
+ BrightnessSliderViewModel(
+ screenBrightnessInteractor = screenBrightnessInteractor,
+ brightnessPolicyEnforcementInteractor = brightnessPolicyEnforcementInteractor,
+ applicationScope = applicationCoroutineScope,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
index cd2710e..fb983f7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.settings.userTracker
import com.android.systemui.user.domain.interactor.selectedUserInteractor
import com.android.systemui.util.mockito.mock
@@ -29,6 +30,7 @@
CommunalSettingsInteractor(
bgScope = applicationCoroutineScope,
bgExecutor = fakeExecutor,
+ bgDispatcher = testDispatcher,
repository = communalSettingsRepository,
userInteractor = selectedUserInteractor,
userTracker = userTracker,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 1a45c42..22b8c8db 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -104,7 +104,8 @@
private val _biometricUnlockState =
MutableStateFlow(BiometricUnlockModel(BiometricUnlockMode.NONE, null))
- override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState
+ override val biometricUnlockState: StateFlow<BiometricUnlockModel> =
+ _biometricUnlockState.asStateFlow()
private val _fingerprintSensorLocation = MutableStateFlow<Point?>(null)
override val fingerprintSensorLocation: Flow<Point?> = _fingerprintSensorLocation
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractorKosmos.kt
new file mode 100644
index 0000000..7a3f925
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractorKosmos.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.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.biometricUnlockInteractor by Fixture {
+ BiometricUnlockInteractor(
+ keyguardRepository = keyguardRepository,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 0b28e3f..6d2d04a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -50,6 +50,7 @@
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.domain.startable.scrimStartable
import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.shared.model.sceneDataSource
import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
@@ -59,6 +60,7 @@
import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
+import com.android.systemui.statusbar.phone.scrimController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
@@ -130,4 +132,6 @@
val shadeInteractor by lazy { kosmos.shadeInteractor }
val ongoingActivityChipsViewModel by lazy { kosmos.ongoingActivityChipsViewModel }
+ val scrimController by lazy { kosmos.scrimController }
+ val scrimStartable by lazy { kosmos.scrimStartable }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/ScrimStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/ScrimStartableKosmos.kt
new file mode 100644
index 0000000..87b6add
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/ScrimStartableKosmos.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.domain.startable
+
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.keyguard.domain.interactor.biometricUnlockInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.phone.dozeServiceHost
+import com.android.systemui.statusbar.phone.scrimController
+import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.scrimStartable by Fixture {
+ ScrimStartable(
+ applicationScope = applicationCoroutineScope,
+ scrimController = scrimController,
+ sceneInteractor = sceneInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
+ keyguardInteractor = keyguardInteractor,
+ occlusionInteractor = sceneContainerOcclusionInteractor,
+ biometricUnlockInteractor = biometricUnlockInteractor,
+ statusBarKeyguardViewManager = statusBarKeyguardViewManager,
+ alternateBouncerInteractor = alternateBouncerInteractor,
+ shadeInteractor = shadeInteractor,
+ brightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor,
+ dozeServiceHost = dozeServiceHost,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
index 297d1d8..0a3a2ee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
@@ -17,7 +17,7 @@
package com.android.systemui.shade.domain.interactor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.util.mockito.mock
@@ -25,7 +25,8 @@
val Kosmos.shadeLockscreenInteractor by
Kosmos.Fixture {
ShadeLockscreenInteractorImpl(
- scope = testScope,
+ applicationScope = applicationCoroutineScope,
+ backgroundScope = applicationCoroutineScope,
shadeInteractor = shadeInteractorImpl,
sceneInteractor = sceneInteractor,
lockIconViewController = mock(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
index 8c5ff1d..c5625e4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
@@ -16,8 +16,12 @@
package com.android.systemui.shade.ui.viewmodel
+import com.android.systemui.brightness.ui.viewmodel.brightnessSliderViewModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.tileGridViewModel
+import com.android.systemui.qs.ui.adapter.qsSceneAdapter
import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneViewModel
val Kosmos.quickSettingsShadeSceneViewModel: QuickSettingsShadeSceneViewModel by
@@ -25,5 +29,9 @@
QuickSettingsShadeSceneViewModel(
applicationScope = applicationCoroutineScope,
overlayShadeViewModel = overlayShadeViewModel,
+ brightnessSliderViewModel = brightnessSliderViewModel,
+ tileGridViewModel = tileGridViewModel,
+ editModeViewModel = editModeViewModel,
+ qsSceneAdapter = qsSceneAdapter,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
index beabaf5..3a70cdf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
@@ -44,7 +44,7 @@
}
@Override
- public void registerContentObserver(Uri uri, boolean notifyDescendants,
+ public void registerContentObserverSync(Uri uri, boolean notifyDescendants,
ContentObserver settingsObserver) {
List<ContentObserver> observers;
mContentObserversAllUsers.putIfAbsent(uri.toString(), new ArrayList<>());
@@ -53,7 +53,7 @@
}
@Override
- public void unregisterContentObserver(ContentObserver settingsObserver) {
+ public void unregisterContentObserverSync(ContentObserver settingsObserver) {
for (Map.Entry<String, List<ContentObserver>> entry :
mContentObserversAllUsers.entrySet()) {
entry.getValue().remove(settingsObserver);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
index a491886..cd219ec 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
@@ -66,7 +66,7 @@
}
@Override
- public void registerContentObserverForUser(Uri uri, boolean notifyDescendants,
+ public void registerContentObserverForUserSync(Uri uri, boolean notifyDescendants,
ContentObserver settingsObserver, int userHandle) {
List<ContentObserver> observers;
if (userHandle == UserHandle.USER_ALL) {
@@ -81,7 +81,7 @@
}
@Override
- public void unregisterContentObserver(ContentObserver settingsObserver) {
+ public void unregisterContentObserverSync(ContentObserver settingsObserver) {
for (SettingsKey key : mContentObservers.keySet()) {
List<ContentObserver> observers = mContentObservers.get(key);
observers.remove(settingsObserver);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt
index 3f51a79..e2d414e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.domain.interactor
+import android.content.applicationContext
import com.android.systemui.bluetooth.bluetoothAdapter
import com.android.systemui.bluetooth.localBluetoothManager
import com.android.systemui.kosmos.Kosmos
@@ -27,6 +28,7 @@
val Kosmos.audioOutputInteractor by
Kosmos.Fixture {
AudioOutputInteractor(
+ applicationContext,
audioRepository,
audioModeInteractor,
testScope.backgroundScope,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt
new file mode 100644
index 0000000..9f11822
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.panel.component.mediaoutput.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.volume.domain.interactor.audioModeInteractor
+import com.android.systemui.volume.domain.interactor.audioOutputInteractor
+import com.android.systemui.volume.mediaDeviceSessionInteractor
+import com.android.systemui.volume.mediaOutputInteractor
+
+val Kosmos.mediaOutputComponentInteractor by
+ Kosmos.Fixture {
+ MediaOutputComponentInteractor(
+ testScope.backgroundScope,
+ mediaDeviceSessionInteractor,
+ audioOutputInteractor,
+ audioModeInteractor,
+ mediaOutputInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.kt
index 6d4576e..2cd6ff2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.kt
@@ -20,11 +20,8 @@
import com.android.internal.logging.uiEventLogger
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
-import com.android.systemui.volume.domain.interactor.audioModeInteractor
-import com.android.systemui.volume.domain.interactor.audioOutputInteractor
-import com.android.systemui.volume.mediaDeviceSessionInteractor
import com.android.systemui.volume.mediaOutputActionsInteractor
-import com.android.systemui.volume.mediaOutputInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.mediaOutputComponentInteractor
var Kosmos.mediaOutputViewModel by
Kosmos.Fixture {
@@ -32,10 +29,7 @@
applicationContext,
testScope.backgroundScope,
mediaOutputActionsInteractor,
- mediaDeviceSessionInteractor,
- audioOutputInteractor,
- audioModeInteractor,
- mediaOutputInteractor,
+ mediaOutputComponentInteractor,
uiEventLogger,
)
}
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 004f37c..2c4bc7c 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -2514,7 +2514,7 @@
public Plane[] getPlanes() {
throwISEIfImageIsInvalid();
if (mPlanes == null) {
- int fenceFd = mParcelImage.fence != null ? mParcelImage.fence.getFd() : -1;
+ int fenceFd = mParcelImage.fence != null ? mParcelImage.fence.detachFd() : -1;
mGraphicBuffer = GraphicBuffer.createFromHardwareBuffer(mParcelImage.buffer);
mPlanes = ImageReader.initializeImagePlanes(mParcelImage.planeCount, mGraphicBuffer,
fenceFd, mParcelImage.format, mParcelImage.timestamp,
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 77decb6..7342b00 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -1748,9 +1748,7 @@
}
public void resetLocked() {
- if (Flags.resettableDynamicProperties()) {
- mAccessibilityServiceInfo.resetDynamicallyConfigurableProperties();
- }
+ mAccessibilityServiceInfo.resetDynamicallyConfigurableProperties();
mSystemSupport.getKeyEventDispatcher().flush(this);
try {
// Clear the proxy in the other process so this
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index fe08338..d09cb3e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -5272,13 +5272,9 @@
//Clip to the window bounds.
Rect windowBounds = mTempRect1;
- if (Flags.focusClickPointWindowBoundsFromA11yWindowInfo()) {
- AccessibilityWindowInfo window = focus.getWindow();
- if (window != null) {
- window.getBoundsInScreen(windowBounds);
- }
- } else {
- getWindowBounds(focus.getWindowId(), windowBounds);
+ AccessibilityWindowInfo window = focus.getWindow();
+ if (window != null) {
+ window.getBoundsInScreen(windowBounds);
}
if (!boundsInScreenBeforeMagnification.intersect(windowBounds)) {
return false;
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 0d0c21d..30e4a3e 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -34,6 +34,7 @@
import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
+import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageCompanionDevice;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
@@ -334,6 +335,12 @@
enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,
"get associations");
+ if (!checkCallerCanManageCompanionDevice(getContext())) {
+ // If the caller neither is system nor holds MANAGE_COMPANION_DEVICES: it needs to
+ // request the feature (also: the caller is the app itself).
+ enforceUsesCompanionDeviceFeature(getContext(), userId, packageName);
+ }
+
return mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
}
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index 3fbd856..d09d7e6 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -347,8 +347,6 @@
* Set association tag.
*/
public void setAssociationTag(int associationId, String tag) {
- Slog.i(TAG, "Setting association tag=[" + tag + "] to id=[" + associationId + "]...");
-
AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
associationId);
association = (new AssociationInfo.Builder(association)).setTag(tag).build();
diff --git a/services/companion/java/com/android/server/companion/association/AssociationStore.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java
index 757abd9..29e8095 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java
@@ -18,7 +18,7 @@
import static com.android.server.companion.utils.MetricUtils.logCreateAssociation;
import static com.android.server.companion.utils.MetricUtils.logRemoveAssociation;
-import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
+import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageAssociationsForPackage;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -457,10 +457,6 @@
/**
* Get association by id with caller checks.
- *
- * If the association is not found, an IllegalArgumentException would be thrown.
- *
- * If the caller can't access the association, a SecurityException would be thrown.
*/
@NonNull
public AssociationInfo getAssociationWithCallerChecks(int associationId) {
@@ -470,9 +466,13 @@
"getAssociationWithCallerChecks() Association id=[" + associationId
+ "] doesn't exist.");
}
- enforceCallerCanManageAssociationsForPackage(mContext, association.getUserId(),
- association.getPackageName(), null);
- return association;
+ if (checkCallerCanManageAssociationsForPackage(mContext, association.getUserId(),
+ association.getPackageName())) {
+ return association;
+ }
+
+ throw new IllegalArgumentException(
+ "The caller can't interact with the association id=[" + associationId + "].");
}
/**
diff --git a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
index 6f0baef..8c1116b 100644
--- a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
@@ -98,6 +98,7 @@
Slog.i(TAG, "Disassociating id=[" + id + "]...");
final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(id);
+
final int userId = association.getUserId();
final String packageName = association.getPackageName();
final String deviceProfile = association.getDeviceProfile();
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 00e049c..026d29c 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -122,6 +122,7 @@
*/
public boolean isPermissionTransferUserConsented(int associationId) {
mAssociationStore.getAssociationWithCallerChecks(associationId);
+
PermissionSyncRequest request = getPermissionSyncRequest(associationId);
if (request == null) {
return false;
@@ -146,12 +147,12 @@
return null;
}
- Slog.i(LOG_TAG, "Creating permission sync intent for userId [" + userId
- + "] associationId [" + associationId + "]");
-
final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
associationId);
+ Slog.i(LOG_TAG, "Creating permission sync intent for userId [" + userId
+ + "] associationId [" + associationId + "]");
+
// Create an internal intent to launch the user consent dialog
final Bundle extras = new Bundle();
PermissionSyncRequest request = new PermissionSyncRequest(associationId);
@@ -219,9 +220,7 @@
* Enable perm sync for the association
*/
public void enablePermissionsSync(int associationId) {
- AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
- int userId = association.getUserId();
+ int userId = mAssociationStore.getAssociationWithCallerChecks(associationId).getUserId();
PermissionSyncRequest request = new PermissionSyncRequest(associationId);
request.setUserConsented(true);
mSystemDataTransferRequestStore.writeRequest(userId, request);
@@ -231,9 +230,7 @@
* Disable perm sync for the association
*/
public void disablePermissionsSync(int associationId) {
- AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
- int userId = association.getUserId();
+ int userId = mAssociationStore.getAssociationWithCallerChecks(associationId).getUserId();
PermissionSyncRequest request = new PermissionSyncRequest(associationId);
request.setUserConsented(false);
mSystemDataTransferRequestStore.writeRequest(userId, request);
@@ -244,9 +241,8 @@
*/
@Nullable
public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
- AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
- int userId = association.getUserId();
+ int userId = mAssociationStore.getAssociationWithCallerChecks(associationId)
+ .getUserId();
List<SystemDataTransferRequest> requests =
mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
associationId);
@@ -263,9 +259,7 @@
*/
public void removePermissionSyncRequest(int associationId) {
Binder.withCleanCallingIdentity(() -> {
- AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
- int userId = association.getUserId();
+ int userId = mAssociationStore.getAssociationById(associationId).getUserId();
mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId);
});
}
diff --git a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
index 796d285..f397814 100644
--- a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
@@ -149,6 +149,21 @@
}
/**
+ * Check if the caller is system UID or the provided user.
+ */
+ public static boolean checkCallerIsSystemOr(@UserIdInt int userId,
+ @NonNull String packageName) {
+ final int callingUid = getCallingUid();
+ if (callingUid == SYSTEM_UID) return true;
+
+ if (getCallingUserId() != userId) return false;
+
+ if (!checkPackage(callingUid, packageName)) return false;
+
+ return true;
+ }
+
+ /**
* Check if the calling user id matches the userId, and if the package belongs to
* the calling uid.
*/
@@ -169,30 +184,21 @@
}
/**
+ * Check if the caller holds the necessary permission to manage companion devices.
+ */
+ public static boolean checkCallerCanManageCompanionDevice(@NonNull Context context) {
+ if (getCallingUid() == SYSTEM_UID) return true;
+
+ return context.checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED;
+ }
+
+ /**
* Require the caller to be able to manage the associations for the package.
*/
public static void enforceCallerCanManageAssociationsForPackage(@NonNull Context context,
@UserIdInt int userId, @NonNull String packageName,
@Nullable String actionDescription) {
- final int callingUid = getCallingUid();
-
- // If the caller is the system
- if (callingUid == SYSTEM_UID) {
- return;
- }
-
- // If caller can manage the package or has the permissions to manage companion devices
- boolean canInteractAcrossUsers = context.checkCallingPermission(INTERACT_ACROSS_USERS)
- == PERMISSION_GRANTED;
- boolean canManageCompanionDevices = context.checkCallingPermission(MANAGE_COMPANION_DEVICES)
- == PERMISSION_GRANTED;
- if (getCallingUserId() == userId) {
- if (checkPackage(callingUid, packageName) || canManageCompanionDevices) {
- return;
- }
- } else if (canInteractAcrossUsers && canManageCompanionDevices) {
- return;
- }
+ if (checkCallerCanManageAssociationsForPackage(context, userId, packageName)) return;
throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
+ "permissions to "
@@ -213,6 +219,25 @@
}
}
+ /**
+ * Check if the caller is either:
+ * <ul>
+ * <li> the package itself
+ * <li> the System ({@link android.os.Process#SYSTEM_UID})
+ * <li> holds {@link Manifest.permission#MANAGE_COMPANION_DEVICES} and, if belongs to a
+ * different user, also holds {@link Manifest.permission#INTERACT_ACROSS_USERS}.
+ * </ul>
+ * @return whether the caller is one of the above.
+ */
+ public static boolean checkCallerCanManageAssociationsForPackage(@NonNull Context context,
+ @UserIdInt int userId, @NonNull String packageName) {
+ if (checkCallerIsSystemOr(userId, packageName)) return true;
+
+ if (!checkCallerCanInteractWithUserId(context, userId)) return false;
+
+ return checkCallerCanManageCompanionDevice(context);
+ }
+
private static boolean checkPackage(@UserIdInt int uid, @NonNull String packageName) {
try {
return getAppOpsService().checkPackage(uid, packageName) == MODE_ALLOWED;
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index db4840d..211f952 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -83,8 +83,6 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.ServiceThread;
-import dalvik.annotation.optimization.NeverCompile;
-
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
@@ -100,6 +98,8 @@
import java.util.Random;
import java.util.Set;
+import dalvik.annotation.optimization.NeverCompile;
+
public final class CachedAppOptimizer {
// Flags stored in the DeviceConfig API.
@@ -2633,7 +2633,7 @@
public void binderError(int debugPid, ProcessRecord app, int code, int flags, int err) {
Slog.w(TAG_AM, "pid " + debugPid + " " + (app == null ? "null" : app.processName)
+ " sent binder code " + code + " with flags " + flags
- + " and got error " + err);
+ + " to frozen apps and got error " + err);
// Do nothing if the binder error callback is not enabled.
// That means the frozen apps in a wrong state will be killed when they are unfrozen later.
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 2184340..b9cdf27 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -1005,7 +1005,7 @@
if (isForeground || foregroundId != 0) {
pw.print(prefix); pw.print("isForeground="); pw.print(isForeground);
pw.print(" foregroundId="); pw.print(foregroundId);
- pw.printf(" types=%08X", foregroundServiceType);
+ pw.printf(" types=0x%08X", foregroundServiceType);
pw.print(" foregroundNoti="); pw.println(foregroundNoti);
if (isShortFgs() && mShortFgsInfo != null) {
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 6bb3eb1..991f94b 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -15,11 +15,7 @@
*/
package com.android.server.audio;
-import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_CARKIT;
import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_DEFAULT;
-import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_HEADSET;
-import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_HEARING_AID;
-import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_SPEAKER;
import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET;
import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_WATCH;
import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_CARKIT;
@@ -149,6 +145,14 @@
private static final int BT_LE_AUDIO_MIN_VOL = 0;
private static final int BT_LE_AUDIO_MAX_VOL = 255;
+ // BtDevice constants currently rolling out under flag protection. Use own
+ // constants instead to avoid mainline dependency from flag library import
+ // TODO(b/335936458): remove once the BtDevice flag is rolled out
+ private static final String DEVICE_TYPE_SPEAKER = "Speaker";
+ private static final String DEVICE_TYPE_HEADSET = "Headset";
+ private static final String DEVICE_TYPE_CARKIT = "Carkit";
+ private static final String DEVICE_TYPE_HEARING_AID = "HearingAid";
+
/**
* Returns a string representation of the scoAudioMode.
*/
diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
index 5690a9e..69bc66f 100644
--- a/services/core/java/com/android/server/display/DisplayAdapter.java
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -135,7 +135,7 @@
float[] alternativeRefreshRates,
@Display.HdrCapabilities.HdrType int[] supportedHdrTypes) {
return new Display.Mode(NEXT_DISPLAY_MODE_ID.getAndIncrement(), width, height, refreshRate,
- vsyncRate, false, alternativeRefreshRates, supportedHdrTypes);
+ vsyncRate, /* isSynthetic= */ false, alternativeRefreshRates, supportedHdrTypes);
}
public interface Listener {
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index baa154d..184ae41 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -47,6 +47,8 @@
private final BrightnessEvent mBrightnessEvent;
private final int mBrightnessAdjustmentFlag;
+ private final boolean mIsUserInitiatedChange;
+
private DisplayBrightnessState(Builder builder) {
mBrightness = builder.getBrightness();
mSdrBrightness = builder.getSdrBrightness();
@@ -60,6 +62,7 @@
mShouldUpdateScreenBrightnessSetting = builder.shouldUpdateScreenBrightnessSetting();
mBrightnessEvent = builder.getBrightnessEvent();
mBrightnessAdjustmentFlag = builder.getBrightnessAdjustmentFlag();
+ mIsUserInitiatedChange = builder.isUserInitiatedChange();
}
/**
@@ -148,6 +151,13 @@
return mBrightnessAdjustmentFlag;
}
+ /**
+ * Gets if the current brightness changes are because of a user initiated change
+ */
+ public boolean isUserInitiatedChange() {
+ return mIsUserInitiatedChange;
+ }
+
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:");
@@ -168,6 +178,7 @@
stringBuilder.append("\n mBrightnessEvent:")
.append(Objects.toString(mBrightnessEvent, "null"));
stringBuilder.append("\n mBrightnessAdjustmentFlag:").append(mBrightnessAdjustmentFlag);
+ stringBuilder.append("\n mIsUserInitiatedChange:").append(mIsUserInitiatedChange);
return stringBuilder.toString();
}
@@ -199,7 +210,8 @@
&& mShouldUpdateScreenBrightnessSetting
== otherState.shouldUpdateScreenBrightnessSetting()
&& Objects.equals(mBrightnessEvent, otherState.getBrightnessEvent())
- && mBrightnessAdjustmentFlag == otherState.getBrightnessAdjustmentFlag();
+ && mBrightnessAdjustmentFlag == otherState.getBrightnessAdjustmentFlag()
+ && mIsUserInitiatedChange == otherState.isUserInitiatedChange();
}
@Override
@@ -207,7 +219,8 @@
return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason,
mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mMinBrightness,
mCustomAnimationRate,
- mShouldUpdateScreenBrightnessSetting, mBrightnessEvent, mBrightnessAdjustmentFlag);
+ mShouldUpdateScreenBrightnessSetting, mBrightnessEvent, mBrightnessAdjustmentFlag,
+ mIsUserInitiatedChange);
}
/**
@@ -236,6 +249,8 @@
public int mBrightnessAdjustmentFlag = 0;
+ private boolean mIsUserInitiatedChange;
+
/**
* Create a builder starting with the values from the specified {@link
* DisplayBrightnessState}.
@@ -257,6 +272,7 @@
state.shouldUpdateScreenBrightnessSetting());
builder.setBrightnessEvent(state.getBrightnessEvent());
builder.setBrightnessAdjustmentFlag(state.getBrightnessAdjustmentFlag());
+ builder.setIsUserInitiatedChange(state.isUserInitiatedChange());
return builder;
}
@@ -464,5 +480,20 @@
mBrightnessAdjustmentFlag = brightnessAdjustmentFlag;
return this;
}
+
+ /**
+ * Gets if the current change is a user initiated change
+ */
+ public boolean isUserInitiatedChange() {
+ return mIsUserInitiatedChange;
+ }
+
+ /**
+ * This is used to set if the current change is a user initiated change
+ */
+ public Builder setIsUserInitiatedChange(boolean isUserInitiatedChange) {
+ mIsUserInitiatedChange = isUserInitiatedChange;
+ return this;
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index d4c0b01..5fd0253 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -49,6 +49,8 @@
import static android.provider.Settings.Secure.RESOLUTION_MODE_HIGH;
import static android.provider.Settings.Secure.RESOLUTION_MODE_UNKNOWN;
+import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
+
import android.Manifest;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
@@ -4972,8 +4974,9 @@
}
final DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked();
- final boolean ownContent = (displayDevice.getDisplayDeviceInfoLocked().flags
- & DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY) != 0;
+ final boolean isRearDisplay = display.getDevicePositionLocked() == POSITION_REAR;
+ final boolean ownContent = ((displayDevice.getDisplayDeviceInfoLocked().flags
+ & DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY) != 0) || isRearDisplay;
// If the display has enabled mirroring, but specified that it will be managed by
// WindowManager, return an invalid display id. This is to ensure we don't
// accidentally select the display id to mirror based on DM logic and instead allow
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index b97053b..8d71c70 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1391,6 +1391,7 @@
// request changes.
final boolean wasShortTermModelActive =
mAutomaticBrightnessStrategy.isShortTermModelActive();
+ boolean userInitiatedChange = displayBrightnessState.isUserInitiatedChange();
boolean allowAutoBrightnessWhileDozing =
mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig();
if (mFlags.offloadControlsDozeAutoBrightness() && mFlags.isDisplayOffloadEnabled()
@@ -1413,13 +1414,14 @@
mPowerRequest.policy,
mDisplayBrightnessController.getLastUserSetScreenBrightness(),
userSetBrightnessChanged);
+
+ // If the brightness is already set then it's been overridden by something other than
+ // the user, or is a temporary adjustment.
+ userInitiatedChange = (Float.isNaN(brightnessState))
+ && (mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged()
+ || userSetBrightnessChanged);
}
- // If the brightness is already set then it's been overridden by something other than the
- // user, or is a temporary adjustment.
- boolean userInitiatedChange = (Float.isNaN(brightnessState))
- && (mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged()
- || userSetBrightnessChanged);
final int autoBrightnessState = mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()
? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index aa17df6..d567331 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -600,6 +600,7 @@
private StrategyExecutionRequest constructStrategyExecutionRequest(
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
float currentScreenBrightness = getCurrentBrightness();
- return new StrategyExecutionRequest(displayPowerRequest, currentScreenBrightness);
+ return new StrategyExecutionRequest(displayPowerRequest, currentScreenBrightness,
+ mUserSetScreenBrightnessUpdated);
}
}
diff --git a/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java b/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java
index 82c0bbf..304640b 100644
--- a/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java
+++ b/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java
@@ -29,10 +29,14 @@
private final float mCurrentScreenBrightness;
+ // Represents if the user set screen brightness was changed or not.
+ private boolean mUserSetBrightnessChanged;
+
public StrategyExecutionRequest(DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
- float currentScreenBrightness) {
+ float currentScreenBrightness, boolean userSetBrightnessChanged) {
mDisplayPowerRequest = displayPowerRequest;
mCurrentScreenBrightness = currentScreenBrightness;
+ mUserSetBrightnessChanged = userSetBrightnessChanged;
}
public DisplayManagerInternal.DisplayPowerRequest getDisplayPowerRequest() {
@@ -43,6 +47,10 @@
return mCurrentScreenBrightness;
}
+ public boolean isUserSetBrightnessChanged() {
+ return mUserSetBrightnessChanged;
+ }
+
@Override
public boolean equals(Object obj) {
if (!(obj instanceof StrategyExecutionRequest)) {
@@ -50,11 +58,13 @@
}
StrategyExecutionRequest other = (StrategyExecutionRequest) obj;
return Objects.equals(mDisplayPowerRequest, other.getDisplayPowerRequest())
- && mCurrentScreenBrightness == other.getCurrentScreenBrightness();
+ && mCurrentScreenBrightness == other.getCurrentScreenBrightness()
+ && mUserSetBrightnessChanged == other.isUserSetBrightnessChanged();
}
@Override
public int hashCode() {
- return Objects.hash(mDisplayPowerRequest, mCurrentScreenBrightness);
+ return Objects.hash(mDisplayPowerRequest, mCurrentScreenBrightness,
+ mUserSetBrightnessChanged);
}
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index 2907364..2b5241f 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -290,6 +290,8 @@
.setBrightnessAdjustmentFlag(mAutoBrightnessAdjustmentReasonsFlags)
.setShouldUpdateScreenBrightnessSetting(
brightness != strategyExecutionRequest.getCurrentScreenBrightness())
+ .setIsUserInitiatedChange(getAutoBrightnessAdjustmentChanged()
+ || strategyExecutionRequest.isUserSetBrightnessChanged())
.build();
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java
index 3463649a..0b92317 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java
@@ -45,6 +45,7 @@
// The fallback brightness might change due to clamping. Make sure we tell the rest
// of the system by updating the setting
.setShouldUpdateScreenBrightnessSetting(true)
+ .setIsUserInitiatedChange(strategyExecutionRequest.isUserSetBrightnessChanged())
.build();
}
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 f923cbc9..f56d803 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -353,7 +353,7 @@
}
/**
- * @return Whether to ignore preferredRefreshRate app request or not
+ * @return Whether to ignore preferredRefreshRate app request conversion to display mode or not
*/
public boolean ignoreAppPreferredRefreshRateRequest() {
return mIgnoreAppPreferredRefreshRate.isEnabled();
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 76a2827..d519748 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -1254,13 +1254,9 @@
* Responsible for keeping track of app requested refresh rates per display
*/
public final class AppRequestObserver {
- private final SparseArray<Display.Mode> mAppRequestedModeByDisplay;
- private final SparseArray<RefreshRateRange> mAppPreferredRefreshRateRangeByDisplay;
private final boolean mIgnorePreferredRefreshRate;
AppRequestObserver(DisplayManagerFlags flags) {
- mAppRequestedModeByDisplay = new SparseArray<>();
- mAppPreferredRefreshRateRangeByDisplay = new SparseArray<>();
mIgnorePreferredRefreshRate = flags.ignoreAppPreferredRefreshRateRequest();
}
@@ -1269,34 +1265,77 @@
*/
public void setAppRequest(int displayId, int modeId, float requestedRefreshRate,
float requestedMinRefreshRateRange, float requestedMaxRefreshRateRange) {
+ Display.Mode requestedMode;
+ synchronized (mLock) {
+ requestedMode = findModeLocked(displayId, modeId, requestedRefreshRate);
+ }
- if (modeId == 0 && requestedRefreshRate != 0 && !mIgnorePreferredRefreshRate) {
+ Vote frameRateVote = getFrameRateVote(
+ requestedMinRefreshRateRange, requestedMaxRefreshRateRange);
+ Vote baseModeRefreshRateVote = getBaseModeVote(requestedMode, requestedRefreshRate);
+ Vote sizeVote = getSizeVote(requestedMode);
+
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
+ frameRateVote);
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+ baseModeRefreshRateVote);
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
+ }
+
+ private Display.Mode findModeLocked(int displayId, int modeId, float requestedRefreshRate) {
+ Display.Mode mode = null;
+ if (modeId != 0) {
+ mode = findAppModeByIdLocked(displayId, modeId);
+ } else if (requestedRefreshRate != 0 && !mIgnorePreferredRefreshRate) { // modeId == 0
// Scan supported modes returned to find a mode with the same
// size as the default display mode but with the specified refresh rate instead.
- Display.Mode mode = findDefaultModeByRefreshRate(displayId, requestedRefreshRate);
- if (mode != null) {
- modeId = mode.getModeId();
- } else {
+ mode = findDefaultModeByRefreshRateLocked(displayId, requestedRefreshRate);
+ if (mode == null) {
Slog.e(TAG, "Couldn't find a mode for the requestedRefreshRate: "
+ requestedRefreshRate + " on Display: " + displayId);
}
}
+ return mode;
+ }
- synchronized (mLock) {
- setAppRequestedModeLocked(displayId, modeId);
- setAppPreferredRefreshRateRangeLocked(displayId, requestedMinRefreshRateRange,
- requestedMaxRefreshRateRange);
+ private Vote getFrameRateVote(float minRefreshRate, float maxRefreshRate) {
+ RefreshRateRange refreshRateRange = null;
+ if (minRefreshRate > 0 || maxRefreshRate > 0) {
+ float max = maxRefreshRate > 0
+ ? maxRefreshRate : Float.POSITIVE_INFINITY;
+ refreshRateRange = new RefreshRateRange(minRefreshRate, max);
+ if (refreshRateRange.min == 0 && refreshRateRange.max == 0) {
+ // minRefreshRate/maxRefreshRate were invalid
+ refreshRateRange = null;
+ }
}
+ return refreshRateRange != null
+ ? Vote.forRenderFrameRates(refreshRateRange.min, refreshRateRange.max) : null;
+ }
+
+ private Vote getSizeVote(@Nullable Display.Mode mode) {
+ return mode != null
+ ? Vote.forSize(mode.getPhysicalWidth(), mode.getPhysicalHeight()) : null;
+ }
+
+ private Vote getBaseModeVote(@Nullable Display.Mode mode, float requestedRefreshRate) {
+ Vote vote = null;
+ if (mode != null) {
+ if (mode.isSynthetic()) {
+ vote = Vote.forRequestedRefreshRate(mode.getRefreshRate());
+ } else {
+ vote = Vote.forBaseModeRefreshRate(mode.getRefreshRate());
+ }
+ } else if (requestedRefreshRate != 0f && mIgnorePreferredRefreshRate) {
+ vote = Vote.forRequestedRefreshRate(requestedRefreshRate);
+ } // !mIgnorePreferredRefreshRate case is handled by findModeLocked
+ return vote;
}
@Nullable
- private Display.Mode findDefaultModeByRefreshRate(int displayId, float refreshRate) {
- Display.Mode[] modes;
- Display.Mode defaultMode;
- synchronized (mLock) {
- modes = mAppSupportedModesByDisplay.get(displayId);
- defaultMode = mDefaultModeByDisplay.get(displayId);
- }
+ private Display.Mode findDefaultModeByRefreshRateLocked(int displayId, float refreshRate) {
+ Display.Mode[] modes = mAppSupportedModesByDisplay.get(displayId);
+ Display.Mode defaultMode = mDefaultModeByDisplay.get(displayId);
for (int i = 0; i < modes.length; i++) {
if (modes[i].matches(defaultMode.getPhysicalWidth(),
defaultMode.getPhysicalHeight(), refreshRate)) {
@@ -1306,69 +1345,6 @@
return null;
}
- private void setAppRequestedModeLocked(int displayId, int modeId) {
- final Display.Mode requestedMode = findAppModeByIdLocked(displayId, modeId);
- if (Objects.equals(requestedMode, mAppRequestedModeByDisplay.get(displayId))) {
- return;
- }
- final Vote baseModeRefreshRateVote;
- final Vote sizeVote;
- if (requestedMode != null) {
- mAppRequestedModeByDisplay.put(displayId, requestedMode);
- sizeVote = Vote.forSize(requestedMode.getPhysicalWidth(),
- requestedMode.getPhysicalHeight());
- if (requestedMode.isSynthetic()) {
- // TODO: for synthetic mode we should not limit frame rate, we must ensure
- // that frame rate is reachable within other Votes constraints
- baseModeRefreshRateVote = Vote.forRenderFrameRates(
- requestedMode.getRefreshRate(), requestedMode.getRefreshRate());
- } else {
- baseModeRefreshRateVote =
- Vote.forBaseModeRefreshRate(requestedMode.getRefreshRate());
- }
- } else {
- mAppRequestedModeByDisplay.remove(displayId);
- baseModeRefreshRateVote = null;
- sizeVote = null;
- }
-
- mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
- baseModeRefreshRateVote);
- mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
- }
-
- private void setAppPreferredRefreshRateRangeLocked(int displayId,
- float requestedMinRefreshRateRange, float requestedMaxRefreshRateRange) {
- final Vote vote;
-
- RefreshRateRange refreshRateRange = null;
- if (requestedMinRefreshRateRange > 0 || requestedMaxRefreshRateRange > 0) {
- float min = requestedMinRefreshRateRange;
- float max = requestedMaxRefreshRateRange > 0
- ? requestedMaxRefreshRateRange : Float.POSITIVE_INFINITY;
- refreshRateRange = new RefreshRateRange(min, max);
- if (refreshRateRange.min == 0 && refreshRateRange.max == 0) {
- // requestedMinRefreshRateRange/requestedMaxRefreshRateRange were invalid
- refreshRateRange = null;
- }
- }
-
- if (Objects.equals(refreshRateRange,
- mAppPreferredRefreshRateRangeByDisplay.get(displayId))) {
- return;
- }
-
- if (refreshRateRange != null) {
- mAppPreferredRefreshRateRangeByDisplay.put(displayId, refreshRateRange);
- vote = Vote.forRenderFrameRates(refreshRateRange.min, refreshRateRange.max);
- } else {
- mAppPreferredRefreshRateRangeByDisplay.remove(displayId);
- vote = null;
- }
- mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
- vote);
- }
-
private Display.Mode findAppModeByIdLocked(int displayId, int modeId) {
Display.Mode[] modes = mAppSupportedModesByDisplay.get(displayId);
if (modes == null) {
@@ -1384,19 +1360,7 @@
private void dumpLocked(PrintWriter pw) {
pw.println(" AppRequestObserver");
- pw.println(" mAppRequestedModeByDisplay:");
- for (int i = 0; i < mAppRequestedModeByDisplay.size(); i++) {
- final int id = mAppRequestedModeByDisplay.keyAt(i);
- final Display.Mode mode = mAppRequestedModeByDisplay.valueAt(i);
- pw.println(" " + id + " -> " + mode);
- }
- pw.println(" mAppPreferredRefreshRateRangeByDisplay:");
- for (int i = 0; i < mAppPreferredRefreshRateRangeByDisplay.size(); i++) {
- final int id = mAppPreferredRefreshRateRangeByDisplay.keyAt(i);
- final RefreshRateRange refreshRateRange =
- mAppPreferredRefreshRateRangeByDisplay.valueAt(i);
- pw.println(" " + id + " -> " + refreshRateRange);
- }
+ pw.println(" mIgnorePreferredRefreshRate: " + mIgnorePreferredRefreshRate);
}
}
diff --git a/services/core/java/com/android/server/display/mode/RequestedRefreshRateVote.java b/services/core/java/com/android/server/display/mode/RequestedRefreshRateVote.java
new file mode 100644
index 0000000..843cf88
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/RequestedRefreshRateVote.java
@@ -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.server.display.mode;
+
+import android.annotation.NonNull;
+
+import java.util.Objects;
+
+class RequestedRefreshRateVote implements Vote {
+ final float mRefreshRate;
+
+ RequestedRefreshRateVote(float refreshRate) {
+ this.mRefreshRate = refreshRate;
+ }
+
+ @Override
+ public void updateSummary(@NonNull VoteSummary summary) {
+ summary.requestedRefreshRates.add(mRefreshRate);
+ }
+
+ @Override
+ public String toString() {
+ return "RequestedRefreshRateVote{ refreshRate=" + mRefreshRate + " }";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof RequestedRefreshRateVote that)) return false;
+ return Float.compare(mRefreshRate, that.mRefreshRate) == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mRefreshRate);
+ }
+}
diff --git a/services/core/java/com/android/server/display/mode/SyntheticModeManager.java b/services/core/java/com/android/server/display/mode/SyntheticModeManager.java
index 5b6bbc5..a83b939 100644
--- a/services/core/java/com/android/server/display/mode/SyntheticModeManager.java
+++ b/services/core/java/com/android/server/display/mode/SyntheticModeManager.java
@@ -32,7 +32,9 @@
*/
public class SyntheticModeManager {
private static final float FLOAT_TOLERANCE = 0.01f;
- private static final float SYNTHETIC_MODE_HIGH_BOUNDARY = 60f + FLOAT_TOLERANCE;
+ private static final float SYNTHETIC_MODE_REFRESH_RATE = 60f;
+ private static final float SYNTHETIC_MODE_HIGH_BOUNDARY =
+ SYNTHETIC_MODE_REFRESH_RATE + FLOAT_TOLERANCE;
private final boolean mSynthetic60HzModesEnabled;
@@ -62,7 +64,7 @@
nextModeId = mode.getModeId();
}
- float divisor = mode.getVsyncRate() / 60f;
+ float divisor = mode.getVsyncRate() / SYNTHETIC_MODE_REFRESH_RATE;
boolean is60HzAchievable = Math.abs(divisor - Math.round(divisor)) < FLOAT_TOLERANCE;
if (is60HzAchievable) {
sizes.put(new Size(mode.getPhysicalWidth(), mode.getPhysicalHeight()),
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
index 3e8b3c5..1ec469c 100644
--- a/services/core/java/com/android/server/display/mode/Vote.java
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -186,6 +186,10 @@
return new BaseModeRefreshRateVote(baseModeRefreshRate);
}
+ static Vote forRequestedRefreshRate(float refreshRate) {
+ return new RequestedRefreshRateVote(refreshRate);
+ }
+
static Vote forSupportedRefreshRates(List<SupportedModeData> supportedModes) {
if (supportedModes.isEmpty()) {
return null;
diff --git a/services/core/java/com/android/server/display/mode/VoteSummary.java b/services/core/java/com/android/server/display/mode/VoteSummary.java
index d4ce892..00a9226 100644
--- a/services/core/java/com/android/server/display/mode/VoteSummary.java
+++ b/services/core/java/com/android/server/display/mode/VoteSummary.java
@@ -23,7 +23,9 @@
import android.view.SurfaceControl;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
final class VoteSummary {
private static final float FLOAT_TOLERANCE = SurfaceControl.RefreshRateRange.FLOAT_TOLERANCE;
@@ -38,7 +40,14 @@
public int minWidth;
public int minHeight;
public boolean disableRefreshRateSwitching;
+ /**
+ * available modes should have mode with specific refresh rate
+ */
public float appRequestBaseModeRefreshRate;
+ /**
+ * requestRefreshRate should be within [minRenderFrameRate, maxRenderFrameRate] range
+ */
+ public Set<Float> requestedRefreshRates = new HashSet<>();
@Nullable
public List<SupportedRefreshRatesVote.RefreshRates> supportedRefreshRates;
@@ -329,6 +338,21 @@
}
return false;
}
+
+ for (Float requestedRefreshRate : requestedRefreshRates) {
+ if (requestedRefreshRate < minRenderFrameRate
+ || requestedRefreshRate > maxRenderFrameRate) {
+ if (mLoggingEnabled) {
+ Slog.w(TAG, "Requested refreshRate is outside frame rate range"
+ + ": requestedRefreshRates=" + requestedRefreshRates
+ + ", requestedRefreshRate=" + requestedRefreshRate
+ + ", minRenderFrameRate=" + minRenderFrameRate
+ + ", maxRenderFrameRate=" + maxRenderFrameRate);
+ }
+ return false;
+ }
+ }
+
return true;
}
@@ -370,6 +394,7 @@
minHeight = 0;
disableRefreshRateSwitching = false;
appRequestBaseModeRefreshRate = 0f;
+ requestedRefreshRates.clear();
supportedRefreshRates = null;
supportedModeIds = null;
if (mLoggingEnabled) {
@@ -393,6 +418,7 @@
+ ", minHeight=" + minHeight
+ ", disableRefreshRateSwitching=" + disableRefreshRateSwitching
+ ", appRequestBaseModeRefreshRate=" + appRequestBaseModeRefreshRate
+ + ", requestRefreshRates=" + requestedRefreshRates
+ ", supportedRefreshRates=" + supportedRefreshRates
+ ", supportedModeIds=" + supportedModeIds
+ ", mIsDisplayResolutionRangeVotingEnabled="
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index 79f1a9c..c19cb03 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -55,7 +55,6 @@
import java.util.List;
import java.util.Objects;
import java.util.OptionalInt;
-import java.util.function.IntConsumer;
// TODO(b/210039666): See if we can make this class thread-safe.
final class HandwritingModeController {
@@ -91,7 +90,6 @@
private boolean mDelegationConnectionlessFlow;
private Runnable mDelegationIdleTimeoutRunnable;
private Handler mDelegationIdleTimeoutHandler;
- private IntConsumer mPointerToolTypeConsumer;
private final Runnable mDiscardDelegationTextRunnable;
private HandwritingEventReceiverSurface mHandwritingSurface;
@@ -99,7 +97,7 @@
@AnyThread
HandwritingModeController(Context context, Looper uiThreadLooper,
- Runnable inkWindowInitRunnable, IntConsumer toolTypeConsumer,
+ Runnable inkWindowInitRunnable,
Runnable discardDelegationTextRunnable) {
mContext = context;
mLooper = uiThreadLooper;
@@ -109,7 +107,6 @@
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mCurrentRequestId = 0;
mInkWindowInitRunnable = inkWindowInitRunnable;
- mPointerToolTypeConsumer = toolTypeConsumer;
mDiscardDelegationTextRunnable = discardDelegationTextRunnable;
}
@@ -389,16 +386,10 @@
"Input Event should not be processed when IME has the spy channel.");
}
- if (!(ev instanceof MotionEvent)) {
+ if (!(ev instanceof MotionEvent event)) {
Slog.wtf(TAG, "Received non-motion event in stylus monitor.");
return false;
}
- final MotionEvent event = (MotionEvent) ev;
- if (mPointerToolTypeConsumer != null && event.getAction() == MotionEvent.ACTION_DOWN) {
- int toolType = event.getToolType(event.getActionIndex());
- // notify IME of change in tool type.
- mPointerToolTypeConsumer.accept(toolType);
- }
if (!event.isStylusPointer()) {
return false;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index dace67f..f61ca61 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -16,6 +16,8 @@
package com.android.server.inputmethod;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -30,6 +32,9 @@
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.server.LocalServices;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
import java.util.Collections;
import java.util.List;
@@ -38,6 +43,16 @@
*/
public abstract class InputMethodManagerInternal {
/**
+ * Indicates that the method is guaranteed to not require {@link ImfLock}.
+ *
+ * <p>You can call this method without worrying about system_server lock layering.</p>
+ */
+ @Retention(SOURCE)
+ @Target({ElementType.METHOD})
+ public @interface ImfLockFree {
+ }
+
+ /**
* Listener for input method list changed events.
*/
public interface InputMethodListListener {
@@ -53,6 +68,7 @@
*
* @param interactive the interactive mode parameter
*/
+ @ImfLockFree
public abstract void setInteractive(boolean interactive);
/**
@@ -61,6 +77,7 @@
* @param reason the reason for hiding the current input method
* @param originatingDisplayId the display ID the request is originated
*/
+ @ImfLockFree
public abstract void hideAllInputMethods(@SoftInputShowHideReason int reason,
int originatingDisplayId);
@@ -143,6 +160,7 @@
*
* @param listener the listener to add
*/
+ @ImfLockFree
public abstract void registerInputMethodListListener(InputMethodListListener listener);
/**
@@ -178,6 +196,7 @@
*
* @param displayId the display hosting the IME window
*/
+ @ImfLockFree
public abstract void removeImeSurface(int displayId);
/**
@@ -188,12 +207,14 @@
* @param disableImeIcon indicates whether IME icon should be enabled or not
* @param displayId the display for which to update the IME window status
*/
+ @ImfLockFree
public abstract void updateImeWindowStatus(boolean disableImeIcon, int displayId);
/**
* Finish stylus handwriting by calling {@link InputMethodService#finishStylusHandwriting()} if
* there is an ongoing handwriting session.
*/
+ @ImfLockFree
public abstract void maybeFinishStylusHandwriting();
/**
@@ -240,10 +261,12 @@
*/
private static final InputMethodManagerInternal NOP =
new InputMethodManagerInternal() {
+ @ImfLockFree
@Override
public void setInteractive(boolean interactive) {
}
+ @ImfLockFree
@Override
public void hideAllInputMethods(@SoftInputShowHideReason int reason,
int originatingDisplayId) {
@@ -282,6 +305,7 @@
int deviceId, @Nullable String imeId) {
}
+ @ImfLockFree
@Override
public void registerInputMethodListListener(InputMethodListListener listener) {
}
@@ -300,10 +324,12 @@
public void onImeParentChanged(int displayId) {
}
+ @ImfLockFree
@Override
public void removeImeSurface(int displayId) {
}
+ @ImfLockFree
@Override
public void updateImeWindowStatus(boolean disableImeIcon, int displayId) {
}
@@ -318,6 +344,7 @@
@UserIdInt int userId) {
}
+ @ImfLockFree
@Override
public void maybeFinishStylusHandwriting() {
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 7513c40..d22438f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -205,7 +205,6 @@
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
-import java.util.function.IntConsumer;
import java.util.function.IntFunction;
/**
@@ -1305,12 +1304,9 @@
com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
mNonPreemptibleInputMethods = mRes.getStringArray(
com.android.internal.R.array.config_nonPreemptibleInputMethods);
- IntConsumer toolTypeConsumer =
- Flags.useHandwritingListenerForTooltype()
- ? toolType -> onUpdateEditorToolType(toolType) : null;
Runnable discardDelegationTextRunnable = () -> discardHandwritingDelegationText();
mHwController = new HandwritingModeController(mContext, thread.getLooper(),
- new InkWindowInitializer(), toolTypeConsumer, discardDelegationTextRunnable);
+ new InkWindowInitializer(), discardDelegationTextRunnable);
registerDeviceListenerAndCheckStylusSupport();
}
}
@@ -3416,8 +3412,10 @@
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
mCurStatsToken = null;
- if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
- curMethod.updateEditorToolType(lastClickToolType);
+ if (Flags.useHandwritingListenerForTooltype()) {
+ maybeReportToolType();
+ } else if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
+ onUpdateEditorToolType(lastClickToolType);
}
mVisibilityApplier.performShowIme(windowToken, statsToken,
mVisibilityStateComputer.getShowFlagsForInputMethodServiceOnly(),
@@ -3431,6 +3429,29 @@
return false;
}
+ @GuardedBy("ImfLock.class")
+ private void maybeReportToolType() {
+ int lastDeviceId = mInputManagerInternal.getLastUsedInputDeviceId();
+ final InputManager im = mContext.getSystemService(InputManager.class);
+ if (im == null) {
+ return;
+ }
+ InputDevice device = im.getInputDevice(lastDeviceId);
+ if (device == null) {
+ return;
+ }
+ int toolType;
+ if (isStylusDevice(device)) {
+ toolType = MotionEvent.TOOL_TYPE_STYLUS;
+ } else if (isFingerDevice(device)) {
+ toolType = MotionEvent.TOOL_TYPE_FINGER;
+ } else {
+ // other toolTypes are irrelevant and reported as unknown.
+ toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+ }
+ onUpdateEditorToolType(toolType);
+ }
+
@Override
public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
@NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
@@ -4285,6 +4306,10 @@
|| inputDevice.supportsSource(InputDevice.SOURCE_BLUETOOTH_STYLUS);
}
+ private static boolean isFingerDevice(InputDevice inputDevice) {
+ return inputDevice.supportsSource(InputDevice.SOURCE_TOUCHSCREEN);
+ }
+
@GuardedBy("ImfLock.class")
private boolean hasSupportedStylusLocked() {
return mStylusIds != null && mStylusIds.size() != 0;
@@ -5475,12 +5500,14 @@
private final class LocalServiceImpl extends InputMethodManagerInternal {
+ @ImfLockFree
@Override
public void setInteractive(boolean interactive) {
// Do everything in handler so as not to block the caller.
mHandler.obtainMessage(MSG_SET_INTERACTIVE, interactive ? 1 : 0, 0).sendToTarget();
}
+ @ImfLockFree
@Override
public void hideAllInputMethods(@SoftInputShowHideReason int reason,
int originatingDisplayId) {
@@ -5566,6 +5593,7 @@
}
}
+ @ImfLockFree
@Override
public void registerInputMethodListListener(InputMethodListListener listener) {
mInputMethodListListeners.addIfAbsent(listener);
@@ -5613,11 +5641,13 @@
}
}
+ @ImfLockFree
@Override
public void removeImeSurface(int displayId) {
mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
}
+ @ImfLockFree
@Override
public void updateImeWindowStatus(boolean disableImeIcon, int displayId) {
mHandler.obtainMessage(MSG_UPDATE_IME_WINDOW_STATUS, disableImeIcon ? 1 : 0, 0)
@@ -5695,6 +5725,7 @@
}
}
+ @ImfLockFree
@Override
public void maybeFinishStylusHandwriting() {
mHandler.removeMessages(MSG_FINISH_HANDWRITING);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index b3ab229..ae3d36a 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -593,7 +593,7 @@
public RebootEscrowManager getRebootEscrowManager(RebootEscrowManager.Callbacks callbacks,
LockSettingsStorage storage) {
return new RebootEscrowManager(mContext, callbacks, storage,
- getHandler(getServiceThread()));
+ getHandler(getServiceThread()), getUserManagerInternal());
}
public int binderGetCallingUid() {
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index e1cd2c5..d0b8990 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -51,16 +51,20 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.widget.RebootEscrowListener;
+import com.android.server.pm.UserManagerInternal;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
+import java.util.Set;
import javax.crypto.SecretKey;
@@ -138,6 +142,7 @@
ERROR_KEYSTORE_FAILURE,
ERROR_NO_NETWORK,
ERROR_TIMEOUT_EXHAUSTED,
+ ERROR_NO_REBOOT_ESCROW_DATA,
})
@Retention(RetentionPolicy.SOURCE)
@interface RebootEscrowErrorCode {
@@ -153,6 +158,7 @@
static final int ERROR_KEYSTORE_FAILURE = 7;
static final int ERROR_NO_NETWORK = 8;
static final int ERROR_TIMEOUT_EXHAUSTED = 9;
+ static final int ERROR_NO_REBOOT_ESCROW_DATA = 10;
private @RebootEscrowErrorCode int mLoadEscrowDataErrorCode = ERROR_NONE;
@@ -222,11 +228,16 @@
private final RebootEscrowKeyStoreManager mKeyStoreManager;
private final LockSettingsStorage mStorage;
private RebootEscrowProviderInterface mRebootEscrowProvider;
+ private final UserManagerInternal mUserManagerInternal;
- Injector(Context context, LockSettingsStorage storage) {
+ Injector(
+ Context context,
+ LockSettingsStorage storage,
+ UserManagerInternal userManagerInternal) {
mContext = context;
mStorage = storage;
mKeyStoreManager = new RebootEscrowKeyStoreManager();
+ mUserManagerInternal = userManagerInternal;
}
private RebootEscrowProviderInterface createRebootEscrowProvider() {
@@ -326,6 +337,10 @@
return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
}
+ public UserManagerInternal getUserManagerInternal() {
+ return mUserManagerInternal;
+ }
+
public RebootEscrowKeyStoreManager getKeyStoreManager() {
return mKeyStoreManager;
}
@@ -402,8 +417,8 @@
}
RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage,
- Handler handler) {
- this(new Injector(context, storage), callbacks, storage, handler);
+ Handler handler, UserManagerInternal userManagerInternal) {
+ this(new Injector(context, storage, userManagerInternal), callbacks, storage, handler);
}
@VisibleForTesting
@@ -451,18 +466,50 @@
onEscrowRestoreComplete(false, attemptCount, retryHandler);
}
- void loadRebootEscrowDataIfAvailable(Handler retryHandler) {
- List<UserInfo> users = mUserManager.getUsers();
- List<UserInfo> rebootEscrowUsers = new ArrayList<>();
+ private List<UserInfo> getUsersToUnlock(List<UserInfo> users) {
+ // System user must be unlocked to unlock any other user
+ if (mCallbacks.isUserSecure(USER_SYSTEM) && !mStorage.hasRebootEscrow(USER_SYSTEM)) {
+ Slog.i(TAG, "No reboot escrow data found for system user");
+ return Collections.emptyList();
+ }
+
+ Set<Integer> noEscrowDataUsers = new HashSet<>();
for (UserInfo user : users) {
- if (mCallbacks.isUserSecure(user.id) && mStorage.hasRebootEscrow(user.id)) {
- rebootEscrowUsers.add(user);
+ if (mCallbacks.isUserSecure(user.id)
+ && !mStorage.hasRebootEscrow(user.id)) {
+ Slog.d(TAG, "No reboot escrow data found for user " + user);
+ noEscrowDataUsers.add(user.id);
}
}
+ List<UserInfo> rebootEscrowUsers = new ArrayList<>();
+ for (UserInfo user : users) {
+ // No lskf, no need to unlock.
+ if (!mCallbacks.isUserSecure(user.id)) {
+ continue;
+ }
+ // Don't unlock if user or user's parent does not have reboot data
+ int userId = user.id;
+ if (noEscrowDataUsers.contains(userId)
+ || noEscrowDataUsers.contains(
+ mInjector.getUserManagerInternal().getProfileParentId(userId))) {
+ continue;
+ }
+ rebootEscrowUsers.add(user);
+ }
+ return rebootEscrowUsers;
+ }
+
+ void loadRebootEscrowDataIfAvailable(Handler retryHandler) {
+ List<UserInfo> users = mUserManager.getUsers();
+ List<UserInfo> rebootEscrowUsers = getUsersToUnlock(users);
+
if (rebootEscrowUsers.isEmpty()) {
Slog.i(TAG, "No reboot escrow data found for users,"
+ " skipping loading escrow data");
+ setLoadEscrowDataErrorCode(ERROR_NO_REBOOT_ESCROW_DATA, retryHandler);
+ reportMetricOnRestoreComplete(
+ /* success= */ false, /* attemptCount= */ 1, retryHandler);
clearMetricsStorage();
return;
}
diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
index 27b8574..d060f8f2 100644
--- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
+++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
@@ -32,12 +32,13 @@
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
+import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
/** Default implementation for {@link DeviceEffectsApplier}. */
class DefaultDeviceEffectsApplier implements DeviceEffectsApplier {
-
+ private static final String TAG = "DeviceEffectsApplier";
private static final String SUPPRESS_AMBIENT_DISPLAY_TOKEN =
"DefaultDeviceEffectsApplier:SuppressAmbientDisplay";
private static final int SATURATION_LEVEL_GRAYSCALE = 0;
@@ -75,28 +76,44 @@
Binder.withCleanCallingIdentity(() -> {
if (mLastAppliedEffects.shouldSuppressAmbientDisplay()
!= effects.shouldSuppressAmbientDisplay()) {
- mPowerManager.suppressAmbientDisplay(SUPPRESS_AMBIENT_DISPLAY_TOKEN,
- effects.shouldSuppressAmbientDisplay());
+ try {
+ mPowerManager.suppressAmbientDisplay(SUPPRESS_AMBIENT_DISPLAY_TOKEN,
+ effects.shouldSuppressAmbientDisplay());
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not change AOD override", e);
+ }
}
if (mLastAppliedEffects.shouldDisplayGrayscale() != effects.shouldDisplayGrayscale()) {
if (mColorDisplayManager != null) {
- mColorDisplayManager.setSaturationLevel(
- effects.shouldDisplayGrayscale() ? SATURATION_LEVEL_GRAYSCALE
- : SATURATION_LEVEL_FULL_COLOR);
+ try {
+ mColorDisplayManager.setSaturationLevel(
+ effects.shouldDisplayGrayscale() ? SATURATION_LEVEL_GRAYSCALE
+ : SATURATION_LEVEL_FULL_COLOR);
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not change grayscale override", e);
+ }
}
}
if (mLastAppliedEffects.shouldDimWallpaper() != effects.shouldDimWallpaper()) {
if (mWallpaperManager != null) {
- mWallpaperManager.setWallpaperDimAmount(
- effects.shouldDimWallpaper() ? WALLPAPER_DIM_AMOUNT_DIMMED
- : WALLPAPER_DIM_AMOUNT_NORMAL);
+ try {
+ mWallpaperManager.setWallpaperDimAmount(
+ effects.shouldDimWallpaper() ? WALLPAPER_DIM_AMOUNT_DIMMED
+ : WALLPAPER_DIM_AMOUNT_NORMAL);
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not change wallpaper override", e);
+ }
}
}
if (mLastAppliedEffects.shouldUseNightMode() != effects.shouldUseNightMode()) {
- updateOrScheduleNightMode(effects.shouldUseNightMode(), origin);
+ try {
+ updateOrScheduleNightMode(effects.shouldUseNightMode(), origin);
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not change dark theme override", e);
+ }
}
});
@@ -131,9 +148,13 @@
private void updateNightModeImmediately(boolean useNightMode) {
Binder.withCleanCallingIdentity(() -> {
- mUiModeManager.setAttentionModeThemeOverlay(
- useNightMode ? MODE_ATTENTION_THEME_OVERLAY_NIGHT
- : MODE_ATTENTION_THEME_OVERLAY_OFF);
+ try {
+ mUiModeManager.setAttentionModeThemeOverlay(
+ useNightMode ? MODE_ATTENTION_THEME_OVERLAY_NIGHT
+ : MODE_ATTENTION_THEME_OVERLAY_OFF);
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not change wallpaper override", e);
+ }
});
}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 3949dfe..1a8e44b 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -1626,8 +1626,8 @@
ApplicationInfo appInfo = null;
try {
- appInfo = mContext.getPackageManager().getApplicationInfo(
- name.getPackageName(), 0);
+ appInfo = mContext.getPackageManager().getApplicationInfoAsUser(
+ name.getPackageName(), 0, userid);
} catch (NameNotFoundException e) {
// Ignore if the package doesn't exist we won't be able to bind to the service.
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index ae29e1b..454bd20 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -149,6 +149,8 @@
private static final String IMPLICIT_RULE_ID_PREFIX = "implicit_"; // + pkg_name
+ private static final int MAX_ICON_RESOURCE_NAME_LENGTH = 1000;
+
/**
* Send new activation AutomaticZenRule statuses to apps with a min target SDK version
*/
@@ -2645,7 +2647,13 @@
requireNonNull(packageName);
try {
final Resources res = mPm.getResourcesForApplication(packageName);
- return res.getResourceName(resId);
+ String resourceName = res.getResourceName(resId);
+ if (resourceName != null && resourceName.length() > MAX_ICON_RESOURCE_NAME_LENGTH) {
+ Slog.e(TAG, "Resource name for ID=" + resId + " in package " + packageName
+ + " is too long (" + resourceName.length() + "); ignoring it");
+ return null;
+ }
+ return resourceName;
} catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
Slog.e(TAG, "Resource name for ID=" + resId + " not found in package " + packageName
+ ". Resource IDs may change when the application is upgraded, and the system"
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index a5cd821..050d44e 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1445,6 +1445,15 @@
.createEvent(DevicePolicyEnums.UNINSTALL_PACKAGE)
.setAdmin(callerPackageName)
.write();
+ } else if (PackageInstallerSession.isEmergencyInstallerEnabled(callerPackageName, snapshot,
+ userId, callingUid)) {
+ // Need to clear the calling identity to get DELETE_PACKAGES permission
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
} else {
ApplicationInfo appInfo = snapshot.getApplicationInfo(callerPackageName, 0, userId);
if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index a904738..0606563 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -316,14 +316,14 @@
private static final int INCREMENTAL_STORAGE_UNHEALTHY_MONITORING_MS = 60000;
/**
- * If an app being installed targets {@link Build.VERSION_CODES#S API 31} and above, the app
- * can be installed without user action.
+ * If an app being installed targets {@link Build.VERSION_CODES#TIRAMISU API 33} and above,
+ * the app can be installed without user action.
* See {@link PackageInstaller.SessionParams#setRequireUserAction} for other conditions required
* to be satisfied for a silent install.
*/
@ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
- private static final long SILENT_INSTALL_ALLOWED = 265131695L;
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ private static final long SILENT_INSTALL_ALLOWED = 325888262L;
/**
* The system supports pre-approval and update ownership features from
@@ -966,7 +966,8 @@
getInstallSource().mInstallerPackageName, mInstallerUid);
}
- private boolean isEmergencyInstallerEnabled(String packageName, Computer snapshot) {
+ static boolean isEmergencyInstallerEnabled(String packageName, Computer snapshot, int userId,
+ int installerUid) {
final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
if (ps == null || ps.getPkg() == null || !ps.isSystem()) {
return false;
@@ -974,7 +975,7 @@
int uid = UserHandle.getUid(userId, ps.getAppId());
String emergencyInstaller = ps.getPkg().getEmergencyInstaller();
if (emergencyInstaller == null || !ArrayUtils.contains(
- snapshot.getPackagesForUid(mInstallerUid), emergencyInstaller)) {
+ snapshot.getPackagesForUid(installerUid), emergencyInstaller)) {
return false;
}
// Only system installers can have an emergency installer
@@ -987,7 +988,7 @@
return false;
}
return (snapshot.checkUidPermission(Manifest.permission.EMERGENCY_INSTALL_PACKAGES,
- mInstallerUid) == PackageManager.PERMISSION_GRANTED);
+ installerUid) == PackageManager.PERMISSION_GRANTED);
}
private static final int USER_ACTION_NOT_NEEDED = 0;
@@ -1075,7 +1076,7 @@
getInstallerPackageName());
final boolean isSelfUpdate = targetPackageUid == mInstallerUid;
final boolean isEmergencyInstall =
- isEmergencyInstallerEnabled(packageName, snapshot);
+ isEmergencyInstallerEnabled(packageName, snapshot, userId, mInstallerUid);
final boolean isPermissionGranted = isInstallPermissionGranted
|| (isUpdatePermissionGranted && isUpdate)
|| (isSelfUpdatePermissionGranted && isSelfUpdate)
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ac754b3..679ab65 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4057,9 +4057,6 @@
return;
}
- // Log the metrics when the component state is changed.
- PackageMetrics.reportComponentStateChanged(computer, componentStateMetricsList, userId);
-
if (isSynchronous) {
flushPackageRestrictionsAsUserInternalLocked(userId);
} else {
@@ -4079,6 +4076,10 @@
}
}
+ // Log the metrics when the component state is changed.
+ PackageMetrics.reportComponentStateChanged(snapshotComputer(), componentStateMetricsList,
+ userId);
+
final long callingId = Binder.clearCallingIdentity();
try {
final Computer newSnapshot = snapshotComputer();
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 0a8b2b2..c40563f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3380,7 +3380,7 @@
params.sessionParams = sessionParams;
// Allowlist all permissions by default
sessionParams.installFlags |= PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS;
- // Set package source to other by default
+ // Set package source to other by default. Can be overridden by "--package-source"
sessionParams.setPackageSource(PackageInstaller.PACKAGE_SOURCE_OTHER);
// Encodes one of the states:
@@ -3567,6 +3567,9 @@
case "--ignore-dexopt-profile":
sessionParams.installFlags |= PackageManager.INSTALL_IGNORE_DEXOPT_PROFILE;
break;
+ case "--package-source":
+ sessionParams.setPackageSource(Integer.parseInt(getNextArg()));
+ break;
default:
throw new IllegalArgumentException("Unknown option " + opt);
}
diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java
index 8f755f4..f9bad59 100644
--- a/services/core/java/com/android/server/vibrator/HalVibration.java
+++ b/services/core/java/com/android/server/vibrator/HalVibration.java
@@ -133,6 +133,7 @@
// Save scale values for debugging purposes.
mScaleLevel = scaler.getScaleLevel(vibrationUsage);
mAdaptiveScale = scaler.getAdaptiveHapticsScale(vibrationUsage);
+ stats.reportAdaptiveScale(mAdaptiveScale);
// Scale all VibrationEffect instances in given CombinedVibration.
CombinedVibration newEffect = mEffectToPlay.transform(scaler::scale, vibrationUsage);
diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java
index 2d00351..dd66809 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStats.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStats.java
@@ -61,6 +61,10 @@
private int mEndedByUsage;
private int mInterruptedUsage;
+ // Vibration parameters.
+ // Set by VibrationThread only (single-threaded).
+ private float mAdaptiveScale;
+
// All following counters are set by VibrationThread only (single-threaded):
// Counts how many times the VibrationEffect was repeated.
private int mRepeatCount;
@@ -188,6 +192,14 @@
}
}
+ /** Report the adaptive scale that was applied to this vibration. */
+ void reportAdaptiveScale(float scale) {
+ // Only report adaptive scale if it was set for this vibration.
+ if (Float.compare(scale, VibrationScaler.ADAPTIVE_SCALE_NONE) != 0) {
+ mAdaptiveScale = scale;
+ }
+ }
+
/** Report the vibration has looped a few more times. */
void reportRepetition(int loops) {
mRepeatCount += loops;
@@ -287,6 +299,7 @@
public final int vibrationType;
public final int usage;
public final int status;
+ public final float adaptiveScale;
public final boolean endedBySameUid;
public final int endedByUsage;
public final int interruptedUsage;
@@ -316,6 +329,7 @@
this.vibrationType = vibrationType;
this.usage = usage;
this.status = status.getProtoEnumValue();
+ this.adaptiveScale = stats.mAdaptiveScale;
endedBySameUid = (uid == stats.mEndedByUid);
endedByUsage = stats.mEndedByUsage;
interruptedUsage = stats.mInterruptedUsage;
@@ -376,7 +390,7 @@
halOnCount, halOffCount, halPerformCount, halSetAmplitudeCount,
halSetExternalControlCount, halSupportedCompositionPrimitivesUsed,
halSupportedEffectsUsed, halUnsupportedCompositionPrimitivesUsed,
- halUnsupportedEffectsUsed, halCompositionSize, halPwleSize);
+ halUnsupportedEffectsUsed, halCompositionSize, halPwleSize, adaptiveScale);
}
private static int[] filteredKeys(SparseBooleanArray supportArray, boolean supported) {
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 09c2493..3dcc7a6 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -1628,6 +1628,12 @@
mStatus = Vibration.Status.RUNNING;
}
+ public void scale(VibrationScaler scaler, int usage) {
+ scale.scaleLevel = scaler.getScaleLevel(usage);
+ scale.adaptiveHapticsScale = scaler.getAdaptiveHapticsScale(usage);
+ stats.reportAdaptiveScale(scale.adaptiveHapticsScale);
+ }
+
public void mute() {
externalVibration.mute();
}
@@ -2044,9 +2050,7 @@
mCurrentExternalVibration = vibHolder;
vibHolder.linkToDeath();
- vibHolder.scale.scaleLevel = mVibrationScaler.getScaleLevel(attrs.getUsage());
- vibHolder.scale.adaptiveHapticsScale =
- mVibrationScaler.getAdaptiveHapticsScale(attrs.getUsage());
+ vibHolder.scale(mVibrationScaler, attrs.getUsage());
}
if (waitForCompletion) {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 207707e..ac2c886 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -28,6 +28,7 @@
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static android.os.Process.INVALID_PID;
import static android.os.Process.INVALID_UID;
+import static android.os.Process.ROOT_UID;
import static android.os.Process.SYSTEM_UID;
import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER;
@@ -385,6 +386,10 @@
return BackgroundStartPrivileges.NONE;
case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
// no explicit choice by the app - let us decide what to do
+ if (callingUid == ROOT_UID || callingUid == SYSTEM_UID) {
+ // root and system must always opt in explicitly
+ return BackgroundStartPrivileges.NONE;
+ }
if (callingPackage != null) {
// determine based on the calling/creating package
boolean changeEnabled = CompatChanges.isChangeEnabled(
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index d4bbe84..61022cc 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1346,10 +1346,13 @@
// In a multi-resumed environment, like in a freeform device, the top
// activity can be resumed, but it might not be the focused app.
- // Set focused app when top activity is resumed
- if (taskDisplayArea.inMultiWindowMode() && taskDisplayArea.mDisplayContent != null
- && taskDisplayArea.mDisplayContent.mFocusedApp != next) {
- taskDisplayArea.mDisplayContent.setFocusedApp(next);
+ // Set focused app when top activity is resumed. However, we shouldn't do it for a
+ // same task because it can break focused state. (e.g. activity embedding)
+ if (taskDisplayArea.inMultiWindowMode() && taskDisplayArea.mDisplayContent != null) {
+ final ActivityRecord focusedApp = taskDisplayArea.mDisplayContent.mFocusedApp;
+ if (focusedApp == null || focusedApp.getTask() != next.getTask()) {
+ taskDisplayArea.mDisplayContent.setFocusedApp(next);
+ }
}
ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Top activity "
+ "resumed %s", next);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
index 7483b43..c7fd979 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
@@ -202,9 +202,9 @@
private String getDefaultSmsPackage() {
//TODO(b/319449037): Unflag the following change.
if (Flags.defaultSmsPersonalAppSuspensionFixEnabled()) {
- return SmsApplication.getDefaultSmsApplicationAsUser(
- mContext, /*updateIfNeeded=*/ false, mContext.getUser())
- .getPackageName();
+ ComponentName defaultSmsApp = SmsApplication.getDefaultSmsApplicationAsUser(
+ mContext, /*updateIfNeeded=*/ false, mContext.getUser());
+ return defaultSmsApp != null ? defaultSmsApp.getPackageName() : null;
} else {
return Telephony.Sms.getDefaultSmsPackage(mContext);
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 927df8b..cfe4e17 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -761,9 +761,6 @@
}
}
- private static final long BINDER_CALLBACK_THROTTLE_MS = 10_100L;
- private long mBinderCallbackLast = -1;
-
private void run() {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
try {
@@ -968,14 +965,6 @@
Binder.setTransactionCallback(new IBinderCallback() {
@Override
public void onTransactionError(int pid, int code, int flags, int err) {
-
- final long now = SystemClock.uptimeMillis();
- if (now < mBinderCallbackLast + BINDER_CALLBACK_THROTTLE_MS) {
- Slog.d(TAG, "Too many transaction errors, throttling freezer binder callback.");
- return;
- }
- mBinderCallbackLast = now;
- Slog.wtfStack(TAG, "Binder Transaction Error");
mActivityManagerService.frozenBinderTransactionDetected(pid, code, flags, err);
}
});
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index b0c7073..3bdcd9b 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -18,6 +18,7 @@
import android.app.AppOpsManager
import android.companion.virtual.VirtualDeviceManager
+import android.os.Binder
import android.os.Handler
import android.os.UserHandle
import android.permission.PermissionManager
@@ -250,7 +251,9 @@
) {
Slog.w(
LOG_TAG,
- "Cannot set UID mode for runtime permission app op, uid = $uid," +
+ "Cannot set UID mode for runtime permission app op, " +
+ " callingUid = ${Binder.getCallingUid()}, " +
+ " uid = $uid," +
" code = ${AppOpsManager.opToName(code)}," +
" mode = ${AppOpsManager.modeToName(mode)}",
RuntimeException()
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
index 8db896b..bba4c8d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -58,6 +58,7 @@
.setShouldUseAutoBrightness(shouldUseAutoBrightness)
.setShouldUpdateScreenBrightnessSetting(shouldUpdateScreenBrightnessSetting)
.setBrightnessAdjustmentFlag(brightnessAdjustmentFlag)
+ .setIsUserInitiatedChange(true)
.build();
assertEquals(displayBrightnessState.getBrightness(), brightness, FLOAT_DELTA);
@@ -109,7 +110,9 @@
.append("\n mBrightnessEvent:")
.append(Objects.toString(displayBrightnessState.getBrightnessEvent(), "null"))
.append("\n mBrightnessAdjustmentFlag:")
- .append(displayBrightnessState.getBrightnessAdjustmentFlag());
+ .append(displayBrightnessState.getBrightnessAdjustmentFlag())
+ .append("\n mIsUserInitiatedChange:")
+ .append(displayBrightnessState.isUserInitiatedChange());
return sb.toString();
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index c5105e7..323ef6a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -121,7 +121,8 @@
any(StrategySelectionRequest.class))).thenReturn(displayBrightnessStrategy);
mDisplayBrightnessController.updateBrightness(displayPowerRequest, targetDisplayState);
verify(displayBrightnessStrategy).updateBrightness(
- eq(new StrategyExecutionRequest(displayPowerRequest, DEFAULT_BRIGHTNESS)));
+ eq(new StrategyExecutionRequest(displayPowerRequest, DEFAULT_BRIGHTNESS,
+ /* userSetBrightnessChanged= */ false)));
assertEquals(mDisplayBrightnessController.getCurrentDisplayBrightnessStrategy(),
displayBrightnessStrategy);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java
index 7a6a911..4d5ff55 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java
@@ -115,7 +115,8 @@
.build();
DisplayBrightnessState updatedDisplayBrightnessState =
mAutoBrightnessFallbackStrategy.updateBrightness(
- new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+ new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+ /* userSetBrightnessChanged= */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index afb5a5c..8a33f34 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -513,9 +513,11 @@
.setBrightnessEvent(brightnessEvent)
.setBrightnessAdjustmentFlag(BrightnessReason.ADJUSTMENT_AUTO)
.setShouldUpdateScreenBrightnessSetting(true)
+ .setIsUserInitiatedChange(true)
.build();
DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy
- .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f));
+ .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f,
+ /* userSetBrightnessChanged= */ true));
assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState);
}
@@ -560,9 +562,11 @@
.setBrightnessEvent(brightnessEvent)
.setBrightnessAdjustmentFlag(BrightnessReason.ADJUSTMENT_AUTO_TEMP)
.setShouldUpdateScreenBrightnessSetting(true)
+ .setIsUserInitiatedChange(true)
.build();
DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy
- .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f));
+ .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f,
+ /* userSetBrightnessChanged= */ true));
assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
index 47f1746..3534325 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
@@ -60,7 +60,8 @@
.build();
DisplayBrightnessState updatedDisplayBrightnessState =
mBoostBrightnessStrategy.updateBrightness(
- new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+ new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+ /* userSetBrightnessChanged= */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
index 9246780..bd6d8be 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
@@ -57,7 +57,8 @@
.build();
DisplayBrightnessState updatedDisplayBrightnessState =
mDozeBrightnessModeStrategy.updateBrightness(
- new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+ new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+ /* userSetBrightnessChanged= */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java
index c4767ae..4cae35d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java
@@ -57,10 +57,12 @@
.setSdrBrightness(currentBrightness)
.setDisplayBrightnessStrategyName(mFallbackBrightnessStrategy.getName())
.setShouldUpdateScreenBrightnessSetting(true)
+ .setIsUserInitiatedChange(true)
.build();
DisplayBrightnessState updatedDisplayBrightnessState =
mFallbackBrightnessStrategy.updateBrightness(
- new StrategyExecutionRequest(displayPowerRequest, currentBrightness));
+ new StrategyExecutionRequest(displayPowerRequest, currentBrightness,
+ /* userSetBrightnessChanged= */ true));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
index 682c9cc..fdaf8f6 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
@@ -61,7 +61,8 @@
.build();
DisplayBrightnessState updatedDisplayBrightnessState =
mFollowerBrightnessStrategy.updateBrightness(
- new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+ new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+ /* userSetBrightnessChanged= */ false));
assertEquals(expectedDisplayBrightnessState, updatedDisplayBrightnessState);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java
index ccf6309..e3c2760 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java
@@ -72,7 +72,8 @@
.build();
DisplayBrightnessState updatedDisplayBrightnessState =
mOffloadBrightnessStrategy.updateBrightness(
- new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+ new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+ /* userSetBrightnessChanged= */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
assertEquals(PowerManager.BRIGHTNESS_INVALID_FLOAT, mOffloadBrightnessStrategy
.getOffloadScreenBrightness(), 0.0f);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
index 8e7b463..ebe407b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
@@ -60,7 +60,8 @@
.build();
DisplayBrightnessState updatedDisplayBrightnessState =
mOverrideBrightnessStrategy.updateBrightness(
- new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+ new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+ /* userSetBrightnessChanged= */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
index e799d0e..4bad569 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
@@ -58,7 +58,8 @@
.build();
DisplayBrightnessState updatedDisplayBrightnessState =
mScreenOffBrightnessModeStrategy.updateBrightness(
- new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+ new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+ /* userSetBrightnessChanged= */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
index aaada41..5410a20 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
@@ -60,7 +60,8 @@
.build();
DisplayBrightnessState updatedDisplayBrightnessState =
mTemporaryBrightnessStrategy.updateBrightness(
- new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+ new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+ /* userSetBrightnessChanged= */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
index cf6146f..34c6ba9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
@@ -117,11 +117,11 @@
BaseModeRefreshRateVote(60f), SizeVote(1000, 1000, 1000, 1000), RenderVote(60f, 90f)),
PREFERRED_REFRESH_RATE(false, 0, 60f, 0f, 0f,
BaseModeRefreshRateVote(60f), SizeVote(1000, 1000, 1000, 1000), null),
- PREFERRED_REFRESH_RATE_IGNORED(true, 0, 60f, 0f, 0f,
- null, null, null),
+ PREFERRED_REFRESH_RATE_IGNORE_BASE_MODE_CONVERSION(true, 0, 60f, 0f, 0f,
+ RequestedRefreshRateVote(60f), null, null),
PREFERRED_REFRESH_RATE_INVALID(false, 0, 25f, 0f, 0f,
null, null, null),
SYNTHETIC_MODE(false, 99, 0f, 0f, 0f,
- RenderVote(45f, 45f), SizeVote(1000, 1000, 1000, 1000), null),
+ RequestedRefreshRateVote(45f), SizeVote(1000, 1000, 1000, 1000), null),
}
}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/RequestedRefreshRateVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/RequestedRefreshRateVoteTest.kt
new file mode 100644
index 0000000..dbe9e4a
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/RequestedRefreshRateVoteTest.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.server.display.mode
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RequestedRefreshRateVoteTest {
+
+ @Test
+ fun `updates requestedRefreshRates`() {
+ val refreshRate = 90f
+ val vote = RequestedRefreshRateVote(refreshRate)
+ val summary = createVotesSummary()
+
+ vote.updateSummary(summary)
+
+ assertThat(summary.requestedRefreshRates).hasSize(1)
+ assertThat(summary.requestedRefreshRates).contains(refreshRate)
+ }
+
+ @Test
+ fun `updates requestedRefreshRates with multiple refresh rates`() {
+ val refreshRate1 = 90f
+ val vote1 = RequestedRefreshRateVote(refreshRate1)
+
+ val refreshRate2 = 60f
+ val vote2 = RequestedRefreshRateVote(refreshRate2)
+
+ val summary = createVotesSummary()
+
+ vote1.updateSummary(summary)
+ vote2.updateSummary(summary)
+
+ assertThat(summary.requestedRefreshRates).hasSize(2)
+ assertThat(summary.requestedRefreshRates).containsExactly(refreshRate1, refreshRate2)
+ }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
index 5da1bb6..dd5e1be 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
@@ -152,13 +152,51 @@
assertThat(result.map { it.modeId }).containsExactlyElementsIn(testCase.expectedModeIds)
}
+
+ @Test
+ fun `summary invalid if has requestedRefreshRate less than minRenederRate`() {
+ val summary = createSummary()
+ summary.requestedRefreshRates = setOf(30f, 90f)
+ summary.minRenderFrameRate = 60f
+ summary.maxRenderFrameRate = 120f
+
+ val result = summary.filterModes(arrayOf(createMode(1, 90f, 90f)))
+
+ assertThat(result).isEmpty()
+ }
+
+ @Test
+ fun `summary invalid if has requestedRefreshRate more than maxRenderFrameRate`() {
+ val summary = createSummary()
+ summary.requestedRefreshRates = setOf(60f, 240f)
+ summary.minRenderFrameRate = 60f
+ summary.maxRenderFrameRate = 120f
+
+ val result = summary.filterModes(arrayOf(createMode(1, 90f, 90f)))
+
+ assertThat(result).isEmpty()
+ }
+
+ @Test
+ fun `summary valid if all requestedRefreshRates inside render rate limits`() {
+ val summary = createSummary()
+ summary.requestedRefreshRates = setOf(60f, 90f)
+ summary.minRenderFrameRate = 60f
+ summary.maxRenderFrameRate = 120f
+
+ val result = summary.filterModes(arrayOf(createMode(1, 90f, 90f)))
+
+ assertThat(result).hasSize(1)
+ }
}
+
+
private fun createMode(modeId: Int, refreshRate: Float, vsyncRate: Float): Display.Mode {
return Display.Mode(modeId, 600, 800, refreshRate, vsyncRate, false,
FloatArray(0), IntArray(0))
}
-private fun createSummary(supportedModesVoteEnabled: Boolean): VoteSummary {
+private fun createSummary(supportedModesVoteEnabled: Boolean = false): VoteSummary {
val summary = createVotesSummary(supportedModesVoteEnabled = supportedModesVoteEnabled)
summary.width = 600
summary.height = 800
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index c4946f0..8914696 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -432,7 +432,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_RESETTABLE_DYNAMIC_PROPERTIES)
public void binderDied_resetA11yServiceInfo() {
final int flag = AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
setServiceBinding(COMPONENT_NAME);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index 64e6236..17b499e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -19,6 +19,7 @@
import static android.content.pm.UserInfo.FLAG_FULL;
import static android.content.pm.UserInfo.FLAG_PRIMARY;
import static android.content.pm.UserInfo.FLAG_PROFILE;
+import static android.content.pm.UserInfo.NO_PROFILE_GROUP_ID;
import static android.os.UserHandle.USER_SYSTEM;
import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_ESCROW_NOT_READY;
@@ -32,6 +33,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyByte;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
@@ -65,6 +67,7 @@
import com.android.internal.widget.RebootEscrowListener;
import com.android.server.locksettings.ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection;
+import com.android.server.pm.UserManagerInternal;
import org.junit.Before;
import org.junit.Test;
@@ -107,6 +110,7 @@
private Context mContext;
private UserManager mUserManager;
+ private UserManagerInternal mUserManagerInternal;
private RebootEscrowManager.Callbacks mCallbacks;
private IRebootEscrow mRebootEscrow;
private ResumeOnRebootServiceConnection mServiceConnection;
@@ -126,13 +130,15 @@
long getCurrentTimeMillis();
void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount,
- int escrowDurationInSeconds, int vbmetaDigestStatus, int durationSinceBootComplete);
+ int escrowDurationInSeconds, int vbmetaDigestStatus,
+ int durationSinceBootComplete);
}
static class MockInjector extends RebootEscrowManager.Injector {
private final IRebootEscrow mRebootEscrow;
private final RebootEscrowProviderInterface mDefaultRebootEscrowProvider;
private final UserManager mUserManager;
+ private final UserManagerInternal mUserManagerInternal;
private final MockableRebootEscrowInjected mInjected;
private final RebootEscrowKeyStoreManager mKeyStoreManager;
private boolean mServerBased;
@@ -141,12 +147,16 @@
private Consumer<ConnectivityManager.NetworkCallback> mNetworkConsumer;
private boolean mWaitForInternet;
- MockInjector(Context context, UserManager userManager,
+ MockInjector(
+ Context context,
+ UserManager userManager,
+ UserManagerInternal userManagerInternal,
IRebootEscrow rebootEscrow,
RebootEscrowKeyStoreManager keyStoreManager,
LockSettingsStorageTestable storage,
MockableRebootEscrowInjected injected) {
- super(context, storage);
+ // TODO: change this
+ super(context, storage, userManagerInternal);
mRebootEscrow = rebootEscrow;
mServerBased = false;
mWaitForInternet = false;
@@ -159,16 +169,20 @@
};
mDefaultRebootEscrowProvider = new RebootEscrowProviderHalImpl(halInjector);
mUserManager = userManager;
+ mUserManagerInternal = userManagerInternal;
mKeyStoreManager = keyStoreManager;
mInjected = injected;
}
- MockInjector(Context context, UserManager userManager,
+ MockInjector(
+ Context context,
+ UserManager userManager,
+ UserManagerInternal userManagerInternal,
ResumeOnRebootServiceConnection serviceConnection,
RebootEscrowKeyStoreManager keyStoreManager,
LockSettingsStorageTestable storage,
MockableRebootEscrowInjected injected) {
- super(context, storage);
+ super(context, storage, userManagerInternal);
mRebootEscrow = null;
mServerBased = true;
mWaitForInternet = false;
@@ -187,6 +201,7 @@
mDefaultRebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(
storage, injector);
mUserManager = userManager;
+ mUserManagerInternal = userManagerInternal;
mKeyStoreManager = keyStoreManager;
mInjected = injected;
}
@@ -202,6 +217,11 @@
}
@Override
+ public UserManagerInternal getUserManagerInternal() {
+ return mUserManagerInternal;
+ }
+
+ @Override
public boolean serverBasedResumeOnReboot() {
return mServerBased;
}
@@ -289,8 +309,8 @@
@Override
public void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount,
- int escrowDurationInSeconds, int vbmetaDigestStatus,
- int durationSinceBootComplete) {
+ int escrowDurationInSeconds, int vbmetaDigestStatus,
+ int durationSinceBootComplete) {
mInjected.reportMetric(success, errorCode, serviceType, attemptCount,
escrowDurationInSeconds, vbmetaDigestStatus, durationSinceBootComplete);
@@ -301,6 +321,7 @@
public void setUp_baseServices() throws Exception {
mContext = new ContextWrapper(InstrumentationRegistry.getContext());
mUserManager = mock(UserManager.class);
+ mUserManagerInternal = mock(UserManagerInternal.class);
mCallbacks = mock(RebootEscrowManager.Callbacks.class);
mRebootEscrow = mock(IRebootEscrow.class);
mServiceConnection = mock(ResumeOnRebootServiceConnection.class);
@@ -314,28 +335,43 @@
new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings"));
ArrayList<UserInfo> users = new ArrayList<>();
- users.add(new UserInfo(PRIMARY_USER_ID, "primary", FLAG_PRIMARY));
- users.add(new UserInfo(WORK_PROFILE_USER_ID, "work", FLAG_PROFILE));
- users.add(new UserInfo(NONSECURE_SECONDARY_USER_ID, "non-secure", FLAG_FULL));
- users.add(new UserInfo(SECURE_SECONDARY_USER_ID, "secure", FLAG_FULL));
+ users.add(createUser(PRIMARY_USER_ID, "primary", FLAG_PRIMARY, PRIMARY_USER_ID));
+ users.add(createUser(WORK_PROFILE_USER_ID, "work", FLAG_PROFILE, PRIMARY_USER_ID));
+ users.add(
+ createUser(
+ NONSECURE_SECONDARY_USER_ID, "non-secure", FLAG_FULL, NO_PROFILE_GROUP_ID));
+ users.add(createUser(SECURE_SECONDARY_USER_ID, "secure", FLAG_FULL, NO_PROFILE_GROUP_ID));
when(mUserManager.getUsers()).thenReturn(users);
when(mCallbacks.isUserSecure(PRIMARY_USER_ID)).thenReturn(true);
when(mCallbacks.isUserSecure(WORK_PROFILE_USER_ID)).thenReturn(true);
when(mCallbacks.isUserSecure(NONSECURE_SECONDARY_USER_ID)).thenReturn(false);
when(mCallbacks.isUserSecure(SECURE_SECONDARY_USER_ID)).thenReturn(true);
mInjected = mock(MockableRebootEscrowInjected.class);
- mMockInjector = new MockInjector(mContext, mUserManager, mRebootEscrow,
- mKeyStoreManager, mStorage, mInjected);
+ mMockInjector =
+ new MockInjector(
+ mContext,
+ mUserManager,
+ mUserManagerInternal,
+ mRebootEscrow,
+ mKeyStoreManager,
+ mStorage,
+ mInjected);
HandlerThread thread = new HandlerThread("RebootEscrowManagerTest");
thread.start();
mHandler = new Handler(thread.getLooper());
mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage, mHandler);
-
}
private void setServerBasedRebootEscrowProvider() throws Exception {
- mMockInjector = new MockInjector(mContext, mUserManager, mServiceConnection,
- mKeyStoreManager, mStorage, mInjected);
+ mMockInjector =
+ new MockInjector(
+ mContext,
+ mUserManager,
+ mUserManagerInternal,
+ mServiceConnection,
+ mKeyStoreManager,
+ mStorage,
+ mInjected);
mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage, mHandler);
}
@@ -352,6 +388,12 @@
waitForHandler();
}
+ private UserInfo createUser(int id, String name, int flag, int profileGroupId) {
+ UserInfo user = new UserInfo(id, name, flag);
+ when(mUserManagerInternal.getProfileParentId(eq(id))).thenReturn(profileGroupId);
+ return user;
+ }
+
@Test
public void prepareRebootEscrow_Success() throws Exception {
RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
@@ -559,6 +601,172 @@
}
@Test
+ public void loadRebootEscrowDataIfAvailable_noDataPrimaryUser_Failure() throws Exception {
+ setServerBasedRebootEscrowProvider();
+ RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+ mService.setRebootEscrowListener(mockListener);
+ mService.prepareRebootEscrow();
+
+ clearInvocations(mServiceConnection);
+
+ // escrow secondary user, don't escrow primary user
+ callToRebootEscrowIfNeededAndWait(SECURE_SECONDARY_USER_ID);
+ verify(mockListener).onPreparedForReboot(eq(true));
+ verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+ when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+ assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded());
+ verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+
+ assertTrue(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID));
+ assertFalse(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
+ assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+ // pretend reboot happens here
+ when(mInjected.getBootCount()).thenReturn(1);
+ ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+ ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
+ doNothing()
+ .when(mInjected)
+ .reportMetric(
+ metricsSuccessCaptor.capture(),
+ metricsErrorCodeCaptor.capture(),
+ eq(2) /* Server based */,
+ eq(1) /* attempt count */,
+ anyInt(),
+ eq(0) /* vbmeta status */,
+ anyInt());
+ mService.loadRebootEscrowDataIfAvailable(null);
+ verify(mServiceConnection, never()).unwrap(any(), anyLong());
+ verify(mCallbacks, never()).onRebootEscrowRestored(anyByte(), any(), anyInt());
+ assertFalse(metricsSuccessCaptor.getValue());
+ assertEquals(
+ Integer.valueOf(RebootEscrowManager.ERROR_NO_REBOOT_ESCROW_DATA),
+ metricsErrorCodeCaptor.getValue());
+ }
+
+ @Test
+ public void loadRebootEscrowDataIfAvailable_noDataSecondaryUser_Success() throws Exception {
+ setServerBasedRebootEscrowProvider();
+ RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+ mService.setRebootEscrowListener(mockListener);
+ mService.prepareRebootEscrow();
+
+ clearInvocations(mServiceConnection);
+
+ // Setup work profile with secondary user as parent.
+ ArrayList<UserInfo> users = new ArrayList<>();
+ users.add(createUser(PRIMARY_USER_ID, "primary", FLAG_PRIMARY, NO_PROFILE_GROUP_ID));
+ users.add(createUser(WORK_PROFILE_USER_ID, "work", FLAG_PROFILE, SECURE_SECONDARY_USER_ID));
+ users.add(
+ createUser(
+ SECURE_SECONDARY_USER_ID, "secure", FLAG_FULL, SECURE_SECONDARY_USER_ID));
+ when(mUserManager.getUsers()).thenReturn(users);
+
+ // escrow primary user and work profile, don't escrow secondary user
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
+ verify(mockListener).onPreparedForReboot(eq(true));
+ verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+ callToRebootEscrowIfNeededAndWait(WORK_PROFILE_USER_ID);
+ verify(mockListener).onPreparedForReboot(eq(true));
+ verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+ when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+ assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded());
+ verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+
+ assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
+ assertFalse(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID));
+ assertTrue(mStorage.hasRebootEscrow(WORK_PROFILE_USER_ID));
+ assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+ // pretend reboot happens here
+ when(mInjected.getBootCount()).thenReturn(1);
+ ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+ doNothing()
+ .when(mInjected)
+ .reportMetric(
+ metricsSuccessCaptor.capture(),
+ eq(0) /* error code */,
+ eq(2) /* Server based */,
+ eq(1) /* attempt count */,
+ anyInt(),
+ eq(0) /* vbmeta status */,
+ anyInt());
+ when(mServiceConnection.unwrap(any(), anyLong()))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ mService.loadRebootEscrowDataIfAvailable(null);
+
+ verify(mServiceConnection).unwrap(any(), anyLong());
+ verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(PRIMARY_USER_ID));
+ verify(mCallbacks, never())
+ .onRebootEscrowRestored(anyByte(), any(), eq(SECURE_SECONDARY_USER_ID));
+ verify(mCallbacks, never())
+ .onRebootEscrowRestored(anyByte(), any(), eq(WORK_PROFILE_USER_ID));
+ verify(mCallbacks, never())
+ .onRebootEscrowRestored(anyByte(), any(), eq(NONSECURE_SECONDARY_USER_ID));
+ assertTrue(metricsSuccessCaptor.getValue());
+ }
+
+ @Test
+ public void loadRebootEscrowDataIfAvailable_noDataWorkProfile_Success() throws Exception {
+ setServerBasedRebootEscrowProvider();
+ RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+ mService.setRebootEscrowListener(mockListener);
+ mService.prepareRebootEscrow();
+
+ clearInvocations(mServiceConnection);
+
+ // escrow primary user and secondary user, don't escrow work profile
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
+ verify(mockListener).onPreparedForReboot(eq(true));
+ verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+ callToRebootEscrowIfNeededAndWait(SECURE_SECONDARY_USER_ID);
+ verify(mockListener).onPreparedForReboot(eq(true));
+ verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+ when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+ assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded());
+ verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+
+ assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
+ assertTrue(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID));
+ assertFalse(mStorage.hasRebootEscrow(WORK_PROFILE_USER_ID));
+ assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+ // pretend reboot happens here
+ when(mInjected.getBootCount()).thenReturn(1);
+ ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+ doNothing()
+ .when(mInjected)
+ .reportMetric(
+ metricsSuccessCaptor.capture(),
+ eq(0) /* error code */,
+ eq(2) /* Server based */,
+ eq(1) /* attempt count */,
+ anyInt(),
+ eq(0) /* vbmeta status */,
+ anyInt());
+ when(mServiceConnection.unwrap(any(), anyLong()))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ mService.loadRebootEscrowDataIfAvailable(null);
+
+ verify(mServiceConnection).unwrap(any(), anyLong());
+ verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(PRIMARY_USER_ID));
+ verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(SECURE_SECONDARY_USER_ID));
+ verify(mCallbacks, never())
+ .onRebootEscrowRestored(anyByte(), any(), eq(WORK_PROFILE_USER_ID));
+ verify(mCallbacks, never())
+ .onRebootEscrowRestored(anyByte(), any(), eq(NONSECURE_SECONDARY_USER_ID));
+ assertTrue(metricsSuccessCaptor.getValue());
+ }
+
+ @Test
public void loadRebootEscrowDataIfAvailable_ServerBased_Success() throws Exception {
setServerBasedRebootEscrowProvider();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
index bfbc81c..4bea95f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -25,10 +25,12 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -322,4 +324,26 @@
argThat(filter -> Intent.ACTION_SCREEN_OFF.equals(filter.getAction(0))),
anyInt());
}
+
+ @Test
+ public void apply_servicesThrow_noCrash() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+ doThrow(new RuntimeException()).when(mPowerManager)
+ .suppressAmbientDisplay(anyString(), anyBoolean());
+ doThrow(new RuntimeException()).when(mColorDisplayManager).setSaturationLevel(anyInt());
+ doThrow(new RuntimeException()).when(mWallpaperManager).setWallpaperDimAmount(anyFloat());
+ doThrow(new RuntimeException()).when(mUiModeManager).setAttentionModeThemeOverlay(anyInt());
+ mApplier = new DefaultDeviceEffectsApplier(mContext);
+
+ ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
+ .setShouldSuppressAmbientDisplay(true)
+ .setShouldDimWallpaper(true)
+ .setShouldDisplayGrayscale(true)
+ .setShouldUseNightMode(true)
+ .build();
+ mApplier.apply(effects, UPDATE_ORIGIN_USER);
+
+ // No crashes
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index e4188b0..72ace84 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -6168,6 +6168,23 @@
.isEqualTo(previousManualZenPolicy);
}
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void addRule_iconIdWithResourceNameTooLong_ignoresIcon() {
+ int resourceId = 999;
+ String veryLongResourceName = "com.android.server.notification:drawable/"
+ + "omg_this_is_one_long_resource_name".repeat(100);
+ when(mResources.getResourceName(resourceId)).thenReturn(veryLongResourceName);
+ when(mResources.getIdentifier(veryLongResourceName, null, null)).thenReturn(resourceId);
+
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ new AutomaticZenRule.Builder("Rule", CONDITION_ID).setIconResId(resourceId).build(),
+ UPDATE_ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ AutomaticZenRule storedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+
+ assertThat(storedRule.getIconResId()).isEqualTo(0);
+ }
+
private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode,
@Nullable ZenPolicy zenPolicy) {
ZenRule rule = new ZenRule();