Merge "Continue to remove code around legacy captions" into main
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 412f2b7..11da20a 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -284,6 +284,8 @@
"100-framework-minus-apex.ravenwood",
"200-kxml2-android",
+ "ravenwood-runtime-common-ravenwood",
+
"android.test.mock.ravenwood",
"ravenwood-helper-runtime",
"hoststubgen-helper-runtime.ravenwood",
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
index 515ddc8..459c286 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
@@ -19,13 +19,16 @@
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;
@@ -80,7 +83,7 @@
private void prepareForNextRun() {
SystemClock.sleep(COOL_OFF_PERIOD_MS);
- ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers");
+ waitCoolDownPeriod();
mStartTimeNs = System.nanoTime();
mPausedDurationNs = 0;
}
@@ -102,7 +105,7 @@
* to avoid unnecessary waiting.
*/
public void resumeTiming() {
- ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers");
+ waitCoolDownPeriod();
resumeTimer();
}
@@ -162,4 +165,56 @@
}
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 762e2af..98ab0c2 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -188,21 +188,6 @@
}
}
- /** 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 {
@@ -239,7 +224,6 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
- waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -254,7 +238,6 @@
mRunner.pauseTiming();
final int userId = createUserNoFlags();
- waitForBroadcastIdle();
runThenWaitForBroadcasts(userId, () -> {
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -309,9 +292,6 @@
preStartUser(userId, numberOfIterationsToSkip);
- waitForBroadcastIdle();
- waitCoolDownPeriod();
-
runThenWaitForBroadcasts(userId, () -> {
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -353,9 +333,6 @@
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
- waitForBroadcastIdle();
- waitCoolDownPeriod();
-
runThenWaitForBroadcasts(userId, () -> {
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -420,7 +397,6 @@
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
- waitCoolDownPeriod();
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -454,7 +430,6 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
- waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -466,6 +441,7 @@
mRunner.pauseTiming();
final int startUser = mAm.getCurrentUser();
final int userId = createUserNoFlags();
+
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -479,27 +455,6 @@
}
}
- /** 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 {
@@ -507,6 +462,7 @@
mRunner.pauseTiming();
final int startUser = mAm.getCurrentUser();
final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ true);
+
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -536,7 +492,6 @@
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
- waitCoolDownPeriod();
Log.d(TAG, "Starting timer");
mRunner.resumeTiming();
@@ -562,7 +517,6 @@
/* useStaticWallpaper */true);
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
- waitCoolDownPeriod();
Log.d(TAG, "Starting timer");
mRunner.resumeTiming();
@@ -606,7 +560,6 @@
final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ false);
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
- waitCoolDownPeriod();
Log.d(TAG, "Starting timer");
mRunner.resumeTiming();
@@ -614,7 +567,6 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
- waitForBroadcastIdle();
switchUserNoCheck(startUser);
mRunner.resumeTimingForNextIteration();
}
@@ -631,7 +583,6 @@
/* useStaticWallpaper */ true);
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
- waitCoolDownPeriod();
Log.d(TAG, "Starting timer");
mRunner.resumeTiming();
@@ -639,7 +590,6 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
- waitForBroadcastIdle();
switchUserNoCheck(startUser);
mRunner.resumeTimingForNextIteration();
}
@@ -675,13 +625,11 @@
@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();
@@ -703,7 +651,7 @@
final int startUser = mAm.getCurrentUser();
final int userId = createUserNoFlags();
- waitForBroadcastIdle();
+ mRunner.waitCoolDownPeriod();
mUserSwitchWaiter.runThenWaitUntilBootCompleted(userId, () -> {
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -726,7 +674,7 @@
final int startUser = ActivityManager.getCurrentUser();
final int userId = createUserNoFlags();
- waitCoolDownPeriod();
+ mRunner.waitCoolDownPeriod();
mUserSwitchWaiter.runThenWaitUntilBootCompleted(userId, () -> {
mRunner.resumeTiming();
Log.d(TAG, "Starting timer");
@@ -752,7 +700,7 @@
switchUser(userId);
}, Intent.ACTION_MEDIA_MOUNTED);
- waitForBroadcastIdle();
+ mRunner.waitCoolDownPeriod();
mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(startUser, () -> {
runThenWaitForBroadcasts(userId, () -> {
mRunner.resumeTiming();
@@ -781,7 +729,7 @@
switchUser(userId);
}, Intent.ACTION_MEDIA_MOUNTED);
- waitCoolDownPeriod();
+ mRunner.waitCoolDownPeriod();
mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(startUser, () -> {
runThenWaitForBroadcasts(userId, () -> {
mRunner.resumeTiming();
@@ -827,7 +775,6 @@
Log.d(TAG, "Stopping timer");
attestTrue("Failed creating profile " + userId, mUm.isManagedProfile(userId));
removeUser(userId);
- waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -868,7 +815,6 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
- waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -913,7 +859,6 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
- waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
removeUser(userId);
@@ -965,7 +910,6 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
- waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -1030,7 +974,6 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
- waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -1071,7 +1014,6 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
- waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -1124,7 +1066,6 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
- waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -1164,7 +1105,6 @@
runThenWaitForBroadcasts(userId, () -> {
startUserInBackgroundAndWaitForUnlock(userId);
}, Intent.ACTION_MEDIA_MOUNTED);
- waitCoolDownPeriod();
mRunner.resumeTiming();
Log.d(TAG, "Starting timer");
@@ -1280,6 +1220,7 @@
* 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);
@@ -1296,7 +1237,7 @@
*/
private void stopUserAfterWaitingForBroadcastIdle(int userId)
throws RemoteException {
- waitForBroadcastIdle();
+ mRunner.waitCoolDownPeriod();
stopUser(userId);
}
@@ -1438,6 +1379,8 @@
*/
private void runThenWaitForBroadcasts(int userId, FunctionalUtils.ThrowingRunnable runnable,
String... actions) {
+ mRunner.waitCoolDownPeriod();
+
final String unreceivedAction =
mBroadcastWaiter.runThenWaitForBroadcasts(userId, runnable, actions);
@@ -1538,28 +1481,4 @@
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/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 1e824a1..2887d22 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1401,17 +1401,9 @@
public static final int RESTRICTION_SUBREASON_MAX_LENGTH = 16;
/**
- * Restriction reason unknown - do not use directly.
- *
- * For use with noteAppRestrictionEnabled()
- * @hide
- */
- public static final int RESTRICTION_REASON_UNKNOWN = 0;
-
- /**
* Restriction reason to be used when this is normal behavior for the state.
*
- * For use with noteAppRestrictionEnabled()
+ * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long)
* @hide
*/
public static final int RESTRICTION_REASON_DEFAULT = 1;
@@ -1420,7 +1412,7 @@
* Restriction reason is some kind of timeout that moves the app to a more restricted state.
* The threshold should specify how long the app was dormant, in milliseconds.
*
- * For use with noteAppRestrictionEnabled()
+ * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long)
* @hide
*/
public static final int RESTRICTION_REASON_DORMANT = 2;
@@ -1429,7 +1421,7 @@
* Restriction reason to be used when removing a restriction due to direct or indirect usage
* of the app, especially to undo any automatic restrictions.
*
- * For use with noteAppRestrictionEnabled()
+ * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long)
* @hide
*/
public static final int RESTRICTION_REASON_USAGE = 3;
@@ -1438,63 +1430,102 @@
* Restriction reason to be used when the user chooses to manually restrict the app, through
* UI or command line interface.
*
- * For use with noteAppRestrictionEnabled()
+ * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long)
* @hide
*/
public static final int RESTRICTION_REASON_USER = 4;
/**
- * Restriction reason to be used when the user chooses to manually restrict the app on being
- * prompted by the OS or some anomaly detection algorithm. For example, if the app is causing
- * high battery drain or affecting system performance and the OS recommends that the user
- * restrict the app.
- *
- * For use with noteAppRestrictionEnabled()
- * @hide
- */
- public static final int RESTRICTION_REASON_USER_NUDGED = 5;
-
- /**
* Restriction reason to be used when the OS automatically detects that the app is causing
* system health issues such as performance degradation, battery drain, high memory usage, etc.
*
- * For use with noteAppRestrictionEnabled()
+ * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long)
* @hide
*/
- public static final int RESTRICTION_REASON_SYSTEM_HEALTH = 6;
+ public static final int RESTRICTION_REASON_SYSTEM_HEALTH = 5;
/**
- * Restriction reason to be used when there is a server-side decision made to restrict an app
- * that is showing widespread problems on user devices, or violating policy in some way.
+ * Restriction reason to be used when app is doing something that is against policy, such as
+ * spamming the user or being deceptive about its intentions.
*
- * For use with noteAppRestrictionEnabled()
+ * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long)
* @hide
*/
- public static final int RESTRICTION_REASON_REMOTE_TRIGGER = 7;
+ public static final int RESTRICTION_REASON_POLICY = 6;
/**
* Restriction reason to be used when some other problem requires restricting the app.
*
- * For use with noteAppRestrictionEnabled()
+ * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long)
* @hide
*/
- public static final int RESTRICTION_REASON_OTHER = 8;
+ public static final int RESTRICTION_REASON_OTHER = 7;
/** @hide */
@IntDef(prefix = { "RESTRICTION_REASON_" }, value = {
- RESTRICTION_REASON_UNKNOWN,
RESTRICTION_REASON_DEFAULT,
RESTRICTION_REASON_DORMANT,
RESTRICTION_REASON_USAGE,
RESTRICTION_REASON_USER,
- RESTRICTION_REASON_USER_NUDGED,
RESTRICTION_REASON_SYSTEM_HEALTH,
- RESTRICTION_REASON_REMOTE_TRIGGER,
+ RESTRICTION_REASON_POLICY,
RESTRICTION_REASON_OTHER
})
@Retention(RetentionPolicy.SOURCE)
public @interface RestrictionReason{}
+ /**
+ * The source of restriction is the user manually choosing to do so.
+ *
+ * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long)
+ * @hide
+ */
+ public static final int RESTRICTION_SOURCE_USER = 1;
+
+ /**
+ * The source of restriction is the user, on being prompted by the system for the specified
+ * reason.
+ *
+ * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long)
+ * @hide
+ */
+ public static final int RESTRICTION_SOURCE_USER_NUDGED = 2;
+
+ /**
+ * The source of restriction is the system.
+ *
+ * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long)
+ * @hide
+ */
+ public static final int RESTRICTION_SOURCE_SYSTEM = 3;
+
+ /**
+ * The source of restriction is the command line interface through the shell or a test.
+ *
+ * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long)
+ * @hide
+ */
+ public static final int RESTRICTION_SOURCE_COMMAND_LINE = 4;
+
+ /**
+ * The source of restriction is a configuration pushed from a server.
+ *
+ * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long)
+ * @hide
+ */
+ public static final int RESTRICTION_SOURCE_REMOTE_TRIGGER = 5;
+
+ /** @hide */
+ @IntDef(prefix = { "RESTRICTION_SOURCE_" }, value = {
+ RESTRICTION_SOURCE_USER,
+ RESTRICTION_SOURCE_USER_NUDGED,
+ RESTRICTION_SOURCE_SYSTEM,
+ RESTRICTION_SOURCE_COMMAND_LINE,
+ RESTRICTION_SOURCE_REMOTE_TRIGGER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RestrictionSource{}
+
/** @hide */
@android.ravenwood.annotation.RavenwoodKeep
public static String restrictionLevelToName(@RestrictionLevel int level) {
@@ -6254,7 +6285,7 @@
* <p>
* The {@code enabled} value determines whether the state is being applied or removed.
* Not all restrictions are actual restrictions. For example,
- * {@link #RESTRICTION_LEVEL_ADAPTIVE} is a normal state, where there is default lifecycle
+ * {@link #RESTRICTION_LEVEL_ADAPTIVE_BUCKET} is a normal state, where there is default lifecycle
* management applied to the app. Also, {@link #RESTRICTION_LEVEL_EXEMPTED} is used when the
* app is being put in a power-save allowlist.
* <p>
@@ -6267,6 +6298,7 @@
* true,
* RESTRICTION_REASON_USER,
* "settings",
+ * RESTRICTION_SOURCE_USER,
* 0);
* </pre>
* Example arguments when app is put in restricted standby bucket for exceeding X hours of jobs:
@@ -6278,6 +6310,7 @@
* true,
* RESTRICTION_REASON_SYSTEM_HEALTH,
* "job_duration",
+ * RESTRICTION_SOURCE_SYSTEM,
* X * 3600 * 1000L);
* </pre>
*
@@ -6295,7 +6328,7 @@
* Examples of system resource usage: wakelock, wakeups, mobile_data,
* binder_calls, memory, excessive_threads, excessive_cpu, gps_scans, etc.
* Examples of user actions: settings, notification, command_line, launch, etc.
- *
+ * @param source the source of the action, from {@code RestrictionSource}
* @param threshold for reasons that are due to exceeding some threshold, the threshold value
* must be specified. The unit of the threshold depends on the reason and/or
* subReason. For time, use milliseconds. For memory, use KB. For count, use
@@ -6308,10 +6341,10 @@
public void noteAppRestrictionEnabled(@NonNull String packageName, int uid,
@RestrictionLevel int restrictionLevel, boolean enabled,
@RestrictionReason int reason,
- @Nullable String subReason, long threshold) {
+ @Nullable String subReason, @RestrictionSource int source, long threshold) {
try {
getService().noteAppRestrictionEnabled(packageName, uid, restrictionLevel, enabled,
- reason, subReason, threshold);
+ reason, subReason, source, threshold);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 76c1ed6..7dbf270 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 = 10_100L;
+ private static final long BINDER_CALLBACK_THROTTLE_MS = 10_100L;
private long mBinderCallbackLast = -1;
/**
@@ -7551,12 +7551,13 @@
@Override
public void onTransactionError(int pid, int code, int flags, int err) {
final long now = SystemClock.uptimeMillis();
- if (now < mBinderCallbackLast + BINDER_CALLBACK_THROTTLE) {
+ if (now < mBinderCallbackLast + BINDER_CALLBACK_THROTTLE_MS) {
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/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index e8b57f2..15b13dc 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -1016,5 +1016,5 @@
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DEVICE_POWER)")
void noteAppRestrictionEnabled(in String packageName, int uid, int restrictionType,
- boolean enabled, int reason, in String subReason, long threshold);
+ boolean enabled, int reason, in String subReason, int source, long threshold);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index dfae48b..4c839f1 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5637,6 +5637,10 @@
contentView.setDrawableTint(R.id.profile_badge, false,
getPrimaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
}
+ contentView.setContentDescription(
+ R.id.profile_badge,
+ mContext.getSystemService(UserManager.class)
+ .getProfileAccessibilityString(mContext.getUserId()));
}
}
@@ -8924,10 +8928,9 @@
sender = stripStyling(sender);
}
- final boolean showOnlySenderName = showOnlySenderName();
-
final CharSequence title;
- boolean isConversationTitleAvailable = !showOnlySenderName && conversationTitle != null;
+ final boolean isConversationTitleAvailable = showConversationTitle()
+ && conversationTitle != null;
if (isConversationTitleAvailable) {
title = conversationTitle;
} else {
@@ -8947,10 +8950,10 @@
}
}
- /** developer settings to always show sender name */
- private boolean showOnlySenderName() {
+ /** (b/342370742) Developer settings to show conversation title. */
+ private boolean showConversationTitle() {
return SystemProperties.getBoolean(
- "persist.compact_heads_up_notification.show_only_sender_name",
+ "persist.compact_heads_up_notification.show_conversation_title_for_group",
false);
}
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 193c524..3f6c81b 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -695,8 +695,8 @@
* {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
*
* @see #getVibrationEffect()
- * @see Vibrator#areEffectsSupported(int...)
- * @see Vibrator#arePrimitivesSupported(int...)
+ * @see android.os.Vibrator#areEffectsSupported(int...)
+ * @see android.os.Vibrator#arePrimitivesSupported(int...)
*/
@FlaggedApi(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API)
public void setVibrationEffect(@Nullable VibrationEffect effect) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index c529f7d..44444b5 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -5842,6 +5842,24 @@
* with {@link #PASSWORD_QUALITY_UNSPECIFIED} on that instance prior to setting complexity
* requirement for the managed profile.
*
+ * Starting from {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, after the password
+ * requirement has been set, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String,
+ * Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was
+ * successfully set or not. This callback will contain:
+ * <ul>
+ * <li> The policy identifier {@link DevicePolicyIdentifiers#PASSWORD_COMPLEXITY_POLICY}
+ * <li> The {@link TargetUser} that this policy relates to
+ * <li> The {@link PolicyUpdateResult}, which will be
+ * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the
+ * reason the policy failed to be set
+ * e.g. {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY})
+ * </ul>
+ * If there has been a change to the policy,
+ * {@link PolicyUpdateReceiver#onPolicyChanged(Context, String, Bundle, TargetUser,
+ * PolicyUpdateResult)} will notify the admin of this change. This callback will contain the
+ * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult}
+ * will contain the reason why the policy changed.
+ *
* @throws SecurityException if the calling application is not a device owner or a profile
* owner.
* @throws IllegalArgumentException if the complexity level is not one of the four above.
@@ -5849,6 +5867,7 @@
* are password requirements specified using {@link #setPasswordQuality(ComponentName, int)}
* on the parent {@code DevicePolicyManager} instance.
*/
+ @SupportsCoexistence
@RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, conditional = true)
public void setRequiredPasswordComplexity(@PasswordComplexity int passwordComplexity) {
if (mService == null) {
@@ -5880,6 +5899,7 @@
* owner.
*/
@PasswordComplexity
+ @SupportsCoexistence
@RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, conditional = true)
public int getRequiredPasswordComplexity() {
if (mService == null) {
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 86b4180..7d5806a 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -328,6 +328,16 @@
}
flag {
+ name: "unmanaged_mode_migration"
+ namespace: "enterprise"
+ description: "Migrate APIs for unmanaged mode"
+ bug: "335624297"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "headless_single_user_fixes"
namespace: "enterprise"
description: "Various fixes for headless single user mode"
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 6ceae17..55c3bb60 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -166,4 +166,11 @@
namespace: "systemui"
description: "[Minimal HUN] Enables the compact heads up notification reply capability for Conversation Notifications"
bug: "336229954"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "remove_remote_views"
+ namespace: "systemui"
+ description: "Removes all custom views"
+ bug: "342602960"
+}
diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl
index 70c25ea..740f593 100644
--- a/core/java/android/app/trust/ITrustManager.aidl
+++ b/core/java/android/app/trust/ITrustManager.aidl
@@ -41,4 +41,5 @@
void unlockedByBiometricForUser(int userId, in BiometricSourceType source);
void clearAllBiometricRecognized(in BiometricSourceType target, int unlockedUser);
boolean isActiveUnlockRunning(int userId);
+ boolean isInSignificantPlace();
}
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index 23b2ea4..88d4d69 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -299,6 +299,20 @@
}
}
+ /**
+ * Returns true if the device is currently in a significant place, and false in all other
+ * circumstances.
+ *
+ * @hide
+ */
+ public boolean isInSignificantPlace() {
+ try {
+ return mService.isInSignificantPlace();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 1eb466c..e3780ed 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -683,7 +683,9 @@
* <p>
* Read-only transactions may run concurrently with other read-only transactions, and if the
* database is in WAL mode, they may also run concurrently with IMMEDIATE or EXCLUSIVE
- * transactions.
+ * transactions. The {@code temp} schema may be modified during a read-only transaction;
+ * if the transaction is {@link #setTransactionSuccessful}, modifications to temp tables may
+ * be visible to some subsequent transactions.
* <p>
* Transactions can be nested. However, the behavior of the transaction is not altered by
* nested transactions. A nested transaction may be any of the three transaction types but if
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index ec67212..b2dcf90 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -739,6 +739,9 @@
* on is done.
*/
void onBlockingScreenOn(Runnable unblocker);
+
+ /** Whether auto brightness update in doze is allowed */
+ boolean allowAutoBrightnessInDoze();
}
/** A session token that associates a internal display with a {@link DisplayOffloader}. */
@@ -749,6 +752,9 @@
/** Whether the session is active. */
boolean isActive();
+ /** Whether auto brightness update in doze is allowed */
+ boolean allowAutoBrightnessInDoze();
+
/**
* Update the brightness from the offload chip.
* @param brightness The brightness value between {@link PowerManager.BRIGHTNESS_MIN} and
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index c611cb9..3e6223a 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1948,6 +1948,10 @@
public static final int SETTLE_TO_ZERO_STATES = 0xffff0000 & ~MOST_INTERESTING_STATES;
+ // STATES bits that are used for Power Stats tracking
+ public static final int IMPORTANT_FOR_POWER_STATS_STATES =
+ STATE_GPS_ON_FLAG | STATE_SENSOR_ON_FLAG | STATE_AUDIO_ON_FLAG;
+
@UnsupportedAppUsage
public int states;
@@ -1988,6 +1992,10 @@
public static final int SETTLE_TO_ZERO_STATES2 = 0xffff0000 & ~MOST_INTERESTING_STATES2;
+ // STATES2 bits that are used for Power Stats tracking
+ public static final int IMPORTANT_FOR_POWER_STATS_STATES2 =
+ STATE2_VIDEO_ON_FLAG | STATE2_FLASHLIGHT_FLAG | STATE2_CAMERA_FLAG;
+
@UnsupportedAppUsage
public int states2;
@@ -2053,6 +2061,8 @@
public static final int EVENT_WAKEUP_AP = 0x0013;
// Event for reporting that a specific partial wake lock has been held for a long duration.
public static final int EVENT_LONG_WAKE_LOCK = 0x0014;
+ // Event for reporting change of some device states, triggered by a specific UID
+ public static final int EVENT_STATE_CHANGE = 0x0015;
// Number of event types.
public static final int EVENT_COUNT = 0x0016;
@@ -3066,13 +3076,13 @@
public static final String[] HISTORY_EVENT_NAMES = new String[] {
"null", "proc", "fg", "top", "sync", "wake_lock_in", "job", "user", "userfg", "conn",
"active", "pkginst", "pkgunin", "alarm", "stats", "pkginactive", "pkgactive",
- "tmpwhitelist", "screenwake", "wakeupap", "longwake", "est_capacity"
+ "tmpwhitelist", "screenwake", "wakeupap", "longwake", "est_capacity", "state"
};
public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] {
"Enl", "Epr", "Efg", "Etp", "Esy", "Ewl", "Ejb", "Eur", "Euf", "Ecn",
"Eac", "Epi", "Epu", "Eal", "Est", "Eai", "Eaa", "Etw",
- "Esw", "Ewa", "Elw", "Eec"
+ "Esw", "Ewa", "Elw", "Eec", "Esc"
};
@FunctionalInterface
@@ -3087,7 +3097,7 @@
sUidToString, sUidToString, sUidToString, sUidToString, sUidToString, sUidToString,
sUidToString, sUidToString, sUidToString, sUidToString, sUidToString, sIntToString,
sUidToString, sUidToString, sUidToString, sUidToString, sUidToString, sUidToString,
- sUidToString, sUidToString, sUidToString, sIntToString
+ sUidToString, sUidToString, sUidToString, sIntToString, sUidToString
};
/**
diff --git a/core/java/android/os/DeadObjectException.java b/core/java/android/os/DeadObjectException.java
index fc3870e..998e295 100644
--- a/core/java/android/os/DeadObjectException.java
+++ b/core/java/android/os/DeadObjectException.java
@@ -26,7 +26,7 @@
* receive this error from an app, at a minimum, you should
* recover by resetting the connection. For instance, you should
* drop the binder, clean up associated state, and reset your
- * connection to the service which through this error. In order
+ * connection to the service which threw this error. In order
* to simplify your error recovery paths, you may also want to
* "simply" restart your process. However, this may not be an
* option if the service you are talking to is unreliable or
@@ -34,9 +34,11 @@
*
* If this isn't from a service death and is instead from a
* low-level binder error, it will be from:
- * - a oneway call queue filling up (too many oneway calls)
- * - from the binder buffer being filled up, so that the transaction
- * is rejected.
+ * <ul>
+ * <li> a one-way call queue filling up (too many one-way calls)
+ * <li> from the binder buffer being filled up, so that the transaction
+ * is rejected.
+ * </ul>
*
* In these cases, more information about the error will be
* logged. However, there isn't a good way to differentiate
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 17dfdda..71957ee 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -52,6 +52,8 @@
import android.util.Log;
import android.util.Slog;
+import com.android.internal.ravenwood.RavenwoodEnvironment;
+
import dalvik.system.VMRuntime;
import libcore.io.IoUtils;
@@ -388,7 +390,6 @@
* new file descriptor shared state such as file position with the
* original file descriptor.
*/
- @RavenwoodThrow(reason = "Requires JNI support")
public static ParcelFileDescriptor dup(FileDescriptor orig) throws IOException {
try {
final FileDescriptor fd = new FileDescriptor();
@@ -406,7 +407,6 @@
* new file descriptor shared state such as file position with the
* original file descriptor.
*/
- @RavenwoodThrow(reason = "Requires JNI support")
public ParcelFileDescriptor dup() throws IOException {
if (mWrapped != null) {
return mWrapped.dup();
@@ -425,7 +425,6 @@
* @return Returns a new ParcelFileDescriptor holding a FileDescriptor
* for a dup of the given fd.
*/
- @RavenwoodThrow(reason = "Requires JNI support")
public static ParcelFileDescriptor fromFd(int fd) throws IOException {
final FileDescriptor original = new FileDescriptor();
setFdInt(original, fd);
@@ -485,7 +484,7 @@
*
* @throws UncheckedIOException if {@link #dup(FileDescriptor)} throws IOException.
*/
- @RavenwoodThrow(reason = "Requires JNI support")
+ @RavenwoodThrow(reason = "Socket.getFileDescriptor$()")
public static ParcelFileDescriptor fromSocket(Socket socket) {
FileDescriptor fd = socket.getFileDescriptor$();
try {
@@ -519,7 +518,7 @@
*
* @throws UncheckedIOException if {@link #dup(FileDescriptor)} throws IOException.
*/
- @RavenwoodThrow(reason = "Requires JNI support")
+ @RavenwoodThrow(reason = "DatagramSocket.getFileDescriptor$()")
public static ParcelFileDescriptor fromDatagramSocket(DatagramSocket datagramSocket) {
FileDescriptor fd = datagramSocket.getFileDescriptor$();
try {
@@ -534,7 +533,6 @@
* ParcelFileDescriptor in the returned array is the read side; the second
* is the write side.
*/
- @RavenwoodThrow(reason = "Requires JNI support")
public static ParcelFileDescriptor[] createPipe() throws IOException {
try {
final FileDescriptor[] fds = Os.pipe2(ifAtLeastQ(O_CLOEXEC));
@@ -556,7 +554,6 @@
* calling {@link #checkError()}, usually after detecting an EOF.
* This can also be used to detect remote crashes.
*/
- @RavenwoodThrow(reason = "Requires JNI support")
public static ParcelFileDescriptor[] createReliablePipe() throws IOException {
try {
final FileDescriptor[] comm = createCommSocketPair();
@@ -573,7 +570,7 @@
* Create two ParcelFileDescriptors structured as a pair of sockets
* connected to each other. The two sockets are indistinguishable.
*/
- @RavenwoodThrow(reason = "Requires JNI support")
+ @RavenwoodThrow(reason = "Os.socketpair()")
public static ParcelFileDescriptor[] createSocketPair() throws IOException {
return createSocketPair(SOCK_STREAM);
}
@@ -581,7 +578,7 @@
/**
* @hide
*/
- @RavenwoodThrow(reason = "Requires JNI support")
+ @RavenwoodThrow(reason = "Os.socketpair()")
public static ParcelFileDescriptor[] createSocketPair(int type) throws IOException {
try {
final FileDescriptor fd0 = new FileDescriptor();
@@ -604,7 +601,7 @@
* calling {@link #checkError()}, usually after detecting an EOF.
* This can also be used to detect remote crashes.
*/
- @RavenwoodThrow(reason = "Requires JNI support")
+ @RavenwoodThrow(reason = "Os.socketpair()")
public static ParcelFileDescriptor[] createReliableSocketPair() throws IOException {
return createReliableSocketPair(SOCK_STREAM);
}
@@ -612,7 +609,7 @@
/**
* @hide
*/
- @RavenwoodThrow(reason = "Requires JNI support")
+ @RavenwoodThrow(reason = "Os.socketpair()")
public static ParcelFileDescriptor[] createReliableSocketPair(int type) throws IOException {
try {
final FileDescriptor[] comm = createCommSocketPair();
@@ -627,7 +624,7 @@
}
}
- @RavenwoodThrow(reason = "Requires JNI support")
+ @RavenwoodThrow(reason = "Os.socketpair()")
private static FileDescriptor[] createCommSocketPair() throws IOException {
try {
// Use SOCK_SEQPACKET so that we have a guarantee that the status
@@ -656,7 +653,7 @@
*/
@UnsupportedAppUsage
@Deprecated
- @RavenwoodThrow(reason = "Requires JNI support")
+ @RavenwoodThrow(blockedBy = MemoryFile.class)
public static ParcelFileDescriptor fromData(byte[] data, String name) throws IOException {
if (data == null) return null;
MemoryFile file = new MemoryFile(name, data.length);
@@ -712,7 +709,7 @@
* @hide
*/
@TestApi
- @RavenwoodThrow(reason = "Requires kernel support")
+ @RavenwoodThrow(reason = "Os.readlink() and Os.stat()")
public static File getFile(FileDescriptor fd) throws IOException {
try {
final String path = Os.readlink("/proc/self/fd/" + getFdInt(fd));
@@ -744,7 +741,7 @@
* Return the total size of the file representing this fd, as determined by
* {@code stat()}. Returns -1 if the fd is not a file.
*/
- @RavenwoodThrow(reason = "Requires JNI support")
+ @RavenwoodThrow(reason = "Os.readlink() and Os.stat()")
public long getStatSize() {
if (mWrapped != null) {
return mWrapped.getStatSize();
@@ -769,7 +766,6 @@
* @hide
*/
@UnsupportedAppUsage
- @RavenwoodThrow(reason = "Requires JNI support")
public long seekTo(long pos) throws IOException {
if (mWrapped != null) {
return mWrapped.seekTo(pos);
@@ -1037,7 +1033,6 @@
* take care of calling {@link ParcelFileDescriptor#close
* ParcelFileDescriptor.close()} for you when the stream is closed.
*/
- @RavenwoodKeepWholeClass
public static class AutoCloseInputStream extends FileInputStream {
private final ParcelFileDescriptor mPfd;
@@ -1326,12 +1321,15 @@
}
- @RavenwoodThrow
+ @RavenwoodReplace
private static boolean isAtLeastQ() {
return (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q);
}
- @RavenwoodThrow
+ private static boolean isAtLeastQ$ravenwood() {
+ return RavenwoodEnvironment.workaround().isTargetSdkAtLeastQ();
+ }
+
private static int ifAtLeastQ(int value) {
return isAtLeastQ() ? value : 0;
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 80d3566..fdce476 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5958,19 +5958,22 @@
/**
* Returns the string used to represent the profile associated with the given userId. This
* string typically includes the profile name used by accessibility services like TalkBack.
- * @hide
*
* @return String representing the accessibility label for the given profile user.
*
* @throws android.content.res.Resources.NotFoundException if the user does not have a label
* defined.
+ *
+ * @see #getBadgedLabelForUser(CharSequence, UserHandle)
+ *
+ * @hide
*/
@UserHandleAware(
requiresAnyOfPermissionsIfNotCallerProfileGroup = {
Manifest.permission.MANAGE_USERS,
Manifest.permission.QUERY_USERS,
Manifest.permission.INTERACT_ACROSS_USERS})
- public String getProfileAccessibilityString(int userId) {
+ public String getProfileAccessibilityString(@UserIdInt int userId) {
if (isManagedProfile(mUserId)) {
DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
dpm.getResources().getString(
@@ -5980,7 +5983,7 @@
return getProfileAccessibilityLabel(userId);
}
- private String getProfileAccessibilityLabel(int userId) {
+ private String getProfileAccessibilityLabel(@UserIdInt int userId) {
try {
final int resourceId = mService.getProfileAccessibilityLabelResId(userId);
return Resources.getSystem().getString(resourceId);
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 25389e5..34fb963 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -193,4 +193,12 @@
namespace: "permissions"
description: "Enable getDeviceId API in OpEventProxyInfo"
bug: "337340961"
+ }
+
+flag {
+ name: "device_aware_app_op_new_schema_enabled"
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "Persist device attributed AppOp accesses on the disk"
+ bug: "308201969"
}
\ No newline at end of file
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index ee5e533..fe7eab7 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -81,6 +81,13 @@
}
flag {
+ name: "significant_places"
+ namespace: "biometrics"
+ description: "Enabled significant place monitoring"
+ bug: "337870680"
+}
+
+flag {
name: "report_primary_auth_attempts"
namespace: "biometrics"
description: "Report primary auth attempts from LockSettingsService"
diff --git a/core/java/android/view/ContentRecordingSession.java b/core/java/android/view/ContentRecordingSession.java
index dc41b70..952c63b 100644
--- a/core/java/android/view/ContentRecordingSession.java
+++ b/core/java/android/view/ContentRecordingSession.java
@@ -58,6 +58,15 @@
/** Can't report (e.g. side loaded app). */
public static final int TARGET_UID_UNKNOWN = -2;
+ /** Task id is not set either because full screen capture or launching a new app */
+ public static final int TASK_ID_UNKNOWN = -1;
+
+ /**
+ * Id of Task that is launched to be captured for a single app capture session. The value may be
+ * {@link #TASK_ID_UNKNOWN} if the session is not for a single app capture.
+ */
+ private int mTaskId = TASK_ID_UNKNOWN;
+
/**
* Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has
* recorded content rendered to its surface.
@@ -115,16 +124,16 @@
/** Returns an instance initialized for task recording. */
public static ContentRecordingSession createTaskSession(
@NonNull IBinder taskWindowContainerToken) {
- return createTaskSession(taskWindowContainerToken, TARGET_UID_UNKNOWN);
+ return createTaskSession(taskWindowContainerToken, TASK_ID_UNKNOWN);
}
/** Returns an instance initialized for task recording. */
public static ContentRecordingSession createTaskSession(
- @NonNull IBinder taskWindowContainerToken, int targetUid) {
+ @NonNull IBinder taskWindowContainerToken, int taskId) {
return new ContentRecordingSession()
.setContentToRecord(RECORD_CONTENT_TASK)
.setTokenToRecord(taskWindowContainerToken)
- .setTargetUid(targetUid);
+ .setTaskId(taskId);
}
/**
@@ -211,12 +220,14 @@
@DataClass.Generated.Member
/* package-private */ ContentRecordingSession(
+ int taskId,
int virtualDisplayId,
@RecordContent int contentToRecord,
int displayToRecord,
@Nullable IBinder tokenToRecord,
boolean waitingForConsent,
int targetUid) {
+ this.mTaskId = taskId;
this.mVirtualDisplayId = virtualDisplayId;
this.mContentToRecord = contentToRecord;
@@ -237,6 +248,15 @@
}
/**
+ * Id of Task that is launched to be captured for a single app capture session. The value may be
+ * {@link #TASK_ID_UNKNOWN} if the session is not for a single app capture.
+ */
+ @DataClass.Generated.Member
+ public int getTaskId() {
+ return mTaskId;
+ }
+
+ /**
* Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has
* recorded content rendered to its surface.
*/
@@ -295,6 +315,16 @@
}
/**
+ * Id of Task that is launched to be captured for a single app capture session. The value may be
+ * {@link #TASK_ID_UNKNOWN} if the session is not for a single app capture.
+ */
+ @DataClass.Generated.Member
+ public @NonNull ContentRecordingSession setTaskId( int value) {
+ mTaskId = value;
+ return this;
+ }
+
+ /**
* Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has
* recorded content rendered to its surface.
*/
@@ -374,6 +404,7 @@
// String fieldNameToString() { ... }
return "ContentRecordingSession { " +
+ "taskId = " + mTaskId + ", " +
"virtualDisplayId = " + mVirtualDisplayId + ", " +
"contentToRecord = " + recordContentToString(mContentToRecord) + ", " +
"displayToRecord = " + mDisplayToRecord + ", " +
@@ -396,6 +427,7 @@
ContentRecordingSession that = (ContentRecordingSession) o;
//noinspection PointlessBooleanExpression
return true
+ && mTaskId == that.mTaskId
&& mVirtualDisplayId == that.mVirtualDisplayId
&& mContentToRecord == that.mContentToRecord
&& mDisplayToRecord == that.mDisplayToRecord
@@ -411,6 +443,7 @@
// int fieldNameHashCode() { ... }
int _hash = 1;
+ _hash = 31 * _hash + mTaskId;
_hash = 31 * _hash + mVirtualDisplayId;
_hash = 31 * _hash + mContentToRecord;
_hash = 31 * _hash + mDisplayToRecord;
@@ -427,9 +460,10 @@
// void parcelFieldName(Parcel dest, int flags) { ... }
byte flg = 0;
- if (mWaitingForConsent) flg |= 0x10;
- if (mTokenToRecord != null) flg |= 0x8;
+ if (mWaitingForConsent) flg |= 0x20;
+ if (mTokenToRecord != null) flg |= 0x10;
dest.writeByte(flg);
+ dest.writeInt(mTaskId);
dest.writeInt(mVirtualDisplayId);
dest.writeInt(mContentToRecord);
dest.writeInt(mDisplayToRecord);
@@ -449,13 +483,15 @@
// static FieldType unparcelFieldName(Parcel in) { ... }
byte flg = in.readByte();
- boolean waitingForConsent = (flg & 0x10) != 0;
+ boolean waitingForConsent = (flg & 0x20) != 0;
+ int taskId = in.readInt();
int virtualDisplayId = in.readInt();
int contentToRecord = in.readInt();
int displayToRecord = in.readInt();
- IBinder tokenToRecord = (flg & 0x8) == 0 ? null : (IBinder) in.readStrongBinder();
+ IBinder tokenToRecord = (flg & 0x10) == 0 ? null : (IBinder) in.readStrongBinder();
int targetUid = in.readInt();
+ this.mTaskId = taskId;
this.mVirtualDisplayId = virtualDisplayId;
this.mContentToRecord = contentToRecord;
@@ -496,6 +532,7 @@
@DataClass.Generated.Member
public static final class Builder {
+ private int mTaskId;
private int mVirtualDisplayId;
private @RecordContent int mContentToRecord;
private int mDisplayToRecord;
@@ -509,13 +546,25 @@
}
/**
+ * Id of Task that is launched to be captured for a single app capture session. The value may be
+ * {@link #TASK_ID_UNKNOWN} if the session is not for a single app capture.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setTaskId(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mTaskId = value;
+ return this;
+ }
+
+ /**
* Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has
* recorded content rendered to its surface.
*/
@DataClass.Generated.Member
public @NonNull Builder setVirtualDisplayId(int value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x1;
+ mBuilderFieldsSet |= 0x2;
mVirtualDisplayId = value;
return this;
}
@@ -526,7 +575,7 @@
@DataClass.Generated.Member
public @NonNull Builder setContentToRecord(@RecordContent int value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x2;
+ mBuilderFieldsSet |= 0x4;
mContentToRecord = value;
return this;
}
@@ -540,7 +589,7 @@
@DataClass.Generated.Member
public @NonNull Builder setDisplayToRecord(int value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x4;
+ mBuilderFieldsSet |= 0x8;
mDisplayToRecord = value;
return this;
}
@@ -554,7 +603,7 @@
@DataClass.Generated.Member
public @NonNull Builder setTokenToRecord(@NonNull IBinder value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x8;
+ mBuilderFieldsSet |= 0x10;
mTokenToRecord = value;
return this;
}
@@ -568,7 +617,7 @@
@DataClass.Generated.Member
public @NonNull Builder setWaitingForConsent(boolean value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x10;
+ mBuilderFieldsSet |= 0x20;
mWaitingForConsent = value;
return this;
}
@@ -579,7 +628,7 @@
@DataClass.Generated.Member
public @NonNull Builder setTargetUid(int value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x20;
+ mBuilderFieldsSet |= 0x40;
mTargetUid = value;
return this;
}
@@ -587,27 +636,31 @@
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull ContentRecordingSession build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x40; // Mark builder used
+ mBuilderFieldsSet |= 0x80; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
- mVirtualDisplayId = INVALID_DISPLAY;
+ mTaskId = TASK_ID_UNKNOWN;
}
if ((mBuilderFieldsSet & 0x2) == 0) {
- mContentToRecord = RECORD_CONTENT_DISPLAY;
+ mVirtualDisplayId = INVALID_DISPLAY;
}
if ((mBuilderFieldsSet & 0x4) == 0) {
- mDisplayToRecord = INVALID_DISPLAY;
+ mContentToRecord = RECORD_CONTENT_DISPLAY;
}
if ((mBuilderFieldsSet & 0x8) == 0) {
- mTokenToRecord = null;
+ mDisplayToRecord = INVALID_DISPLAY;
}
if ((mBuilderFieldsSet & 0x10) == 0) {
- mWaitingForConsent = false;
+ mTokenToRecord = null;
}
if ((mBuilderFieldsSet & 0x20) == 0) {
+ mWaitingForConsent = false;
+ }
+ if ((mBuilderFieldsSet & 0x40) == 0) {
mTargetUid = TARGET_UID_UNKNOWN;
}
ContentRecordingSession o = new ContentRecordingSession(
+ mTaskId,
mVirtualDisplayId,
mContentToRecord,
mDisplayToRecord,
@@ -618,7 +671,7 @@
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x40) != 0) {
+ if ((mBuilderFieldsSet & 0x80) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -626,10 +679,10 @@
}
@DataClass.Generated(
- time = 1697456140720L,
+ time = 1716481148184L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/ContentRecordingSession.java",
- inputSignatures = "public static final int RECORD_CONTENT_DISPLAY\npublic static final int RECORD_CONTENT_TASK\npublic static final int TARGET_UID_FULL_SCREEN\npublic static final int TARGET_UID_UNKNOWN\nprivate int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate boolean mWaitingForConsent\nprivate int mTargetUid\npublic static android.view.ContentRecordingSession createDisplaySession(int)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder,int)\npublic static boolean isValid(android.view.ContentRecordingSession)\npublic static boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)")
+ inputSignatures = "public static final int RECORD_CONTENT_DISPLAY\npublic static final int RECORD_CONTENT_TASK\npublic static final int TARGET_UID_FULL_SCREEN\npublic static final int TARGET_UID_UNKNOWN\npublic static final int TASK_ID_UNKNOWN\nprivate int mTaskId\nprivate int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate boolean mWaitingForConsent\nprivate int mTargetUid\npublic static android.view.ContentRecordingSession createDisplaySession(int)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder,int)\npublic static boolean isValid(android.view.ContentRecordingSession)\npublic static boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 1fc98cf..a9846fb 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1262,15 +1262,11 @@
mHost.getInputMethodManager(), null /* icProto */);
}
- final var statsToken = (types & ime()) == 0 ? null
- : ImeTracker.forLogging().onStart(ImeTracker.TYPE_USER,
- ImeTracker.ORIGIN_CLIENT,
- SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION,
- mHost.isHandlingPointerEvent() /* fromUser */);
+ // TODO(b/342111149): Create statsToken here once ImeTracker#onStart becomes async.
controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs,
interpolator, animationType,
getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack),
- false /* useInsetsAnimationThread */, statsToken);
+ false /* useInsetsAnimationThread */, null);
}
private void controlAnimationUnchecked(@InsetsType int types,
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 1cd7d34..0bdb4ad 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -47,6 +47,7 @@
import android.graphics.Region;
import android.gui.DropInputMode;
import android.gui.StalledTransactionInfo;
+import android.gui.TrustedOverlay;
import android.hardware.DataSpace;
import android.hardware.HardwareBuffer;
import android.hardware.OverlayProperties;
@@ -165,7 +166,7 @@
float maxStretchAmountX, float maxStretchAmountY, float childRelativeLeft,
float childRelativeTop, float childRelativeRight, float childRelativeBottom);
private static native void nativeSetTrustedOverlay(long transactionObj, long nativeObject,
- boolean isTrustedOverlay);
+ int isTrustedOverlay);
private static native void nativeSetDropInputMode(
long transactionObj, long nativeObject, int flags);
private static native void nativeSetCanOccludePresentation(long transactionObj,
@@ -302,6 +303,7 @@
long desiredPresentTimeNanos);
private static native void nativeSetFrameTimeline(long transactionObj,
long vsyncId);
+ private static native void nativeNotifyShutdown();
/**
* Transforms that can be applied to buffers as they are displayed to a window.
@@ -4303,13 +4305,37 @@
}
/**
- * Sets the trusted overlay state on this SurfaceControl and it is inherited to all the
- * children. The caller must hold the ACCESS_SURFACE_FLINGER permission.
+ * @see Transaction#setTrustedOverlay(SurfaceControl, int)
* @hide
*/
public Transaction setTrustedOverlay(SurfaceControl sc, boolean isTrustedOverlay) {
+ return setTrustedOverlay(sc,
+ isTrustedOverlay ? TrustedOverlay.ENABLED : TrustedOverlay.UNSET);
+ }
+
+ /**
+ * Trusted overlay state prevents SurfaceControl from being considered as obscuring for
+ * input occlusion detection purposes. The caller must hold the
+ * ACCESS_SURFACE_FLINGER permission. See {@code TrustedOverlay}.
+ * <p>
+ * Arguments:
+ * {@code TrustedOverlay.UNSET} - The default value, SurfaceControl will inherit the state
+ * from its parents. If the parent state is also {@code TrustedOverlay.UNSET}, the layer
+ * will be considered as untrusted.
+ * <p>
+ * {@code TrustedOverlay.DISABLED} - Treats this SurfaceControl and all its children as an
+ * untrusted overlay. This will override any state set by its parent SurfaceControl.
+ * <p>
+ * {@code TrustedOverlay.ENABLED} - Treats this SurfaceControl and all its children as a
+ * trusted overlay unless the child SurfaceControl explicitly disables its trusted state
+ * via {@code TrustedOverlay.DISABLED}.
+ * <p>
+ * @hide
+ */
+ public Transaction setTrustedOverlay(SurfaceControl sc,
+ @TrustedOverlay int trustedOverlay) {
checkPreconditions(sc);
- nativeSetTrustedOverlay(mNativeObject, sc.mNativeObject, isTrustedOverlay);
+ nativeSetTrustedOverlay(mNativeObject, sc.mNativeObject, trustedOverlay);
return this;
}
@@ -4765,4 +4791,11 @@
return nativeGetStalledTransactionInfo(pid);
}
+ /**
+ * Notify the SurfaceFlinger to capture transaction traces when shutdown.
+ * @hide
+ */
+ public static void notifyShutdown() {
+ nativeNotifyShutdown();
+ }
}
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 5ba2f6c..6f4dd4e 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -93,11 +93,17 @@
ITaskFragmentOrganizerController getTaskFragmentOrganizerController();
/**
- * Registers a transition player with Core. There is only one of these at a time and calling
- * this will replace the existing one if set.
+ * Registers a transition player with Core. There is only one of these active at a time so
+ * calling this will replace the existing one (if set) until it is unregistered.
*/
void registerTransitionPlayer(in ITransitionPlayer player);
+ /**
+ * Un-registers a transition player from Core. This will restore whichever player was active
+ * prior to registering this one.
+ */
+ void unregisterTransitionPlayer(in ITransitionPlayer player);
+
/** @return An interface enabling the transition players to report its metrics. */
ITransitionMetricsReporter getTransitionMetricsReporter();
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 2dc2cbc..5c5da49 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -155,7 +155,7 @@
* @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
- public void registerTransitionPlayer(@Nullable ITransitionPlayer player) {
+ public void registerTransitionPlayer(@NonNull ITransitionPlayer player) {
try {
getWindowOrganizerController().registerTransitionPlayer(player);
} catch (RemoteException e) {
@@ -164,6 +164,19 @@
}
/**
+ * Unregister a previously-registered ITransitionPlayer.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+ public void unregisterTransitionPlayer(@NonNull ITransitionPlayer player) {
+ try {
+ getWindowOrganizerController().unregisterTransitionPlayer(player);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* @see TransitionMetrics
* @hide
*/
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index 97f8084..9029685 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -68,6 +68,7 @@
mTarget = intent.getParcelableExtra(Intent.EXTRA_INTENT,
android.content.IntentSender.class);
String targetPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ Log.i(TAG, "Unlaunchable activity for target package: " + targetPackageName);
final UserManager userManager = UserManager.get(this);
if (mUserId == UserHandle.USER_NULL) {
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index e6af64a..244165f 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -16,6 +16,10 @@
package com.android.internal.os;
+import static android.os.BatteryStats.HistoryItem.EVENT_FLAG_FINISH;
+import static android.os.BatteryStats.HistoryItem.EVENT_FLAG_START;
+import static android.os.BatteryStats.HistoryItem.EVENT_STATE_CHANGE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.BatteryConsumer;
@@ -1449,6 +1453,21 @@
}
/**
+ * Records an event when some state flag changes to true.
+ */
+ public void recordStateStartEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags,
+ int uid, String name) {
+ synchronized (this) {
+ mHistoryCur.states |= stateFlags;
+ mHistoryCur.eventCode = EVENT_STATE_CHANGE | EVENT_FLAG_START;
+ mHistoryCur.eventTag = mHistoryCur.localEventTag;
+ mHistoryCur.eventTag.uid = uid;
+ mHistoryCur.eventTag.string = name;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
* Records an event when some state flag changes to false.
*/
public void recordStateStopEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
@@ -1459,6 +1478,21 @@
}
/**
+ * Records an event when some state flag changes to false.
+ */
+ public void recordStateStopEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags,
+ int uid, String name) {
+ synchronized (this) {
+ mHistoryCur.states &= ~stateFlags;
+ mHistoryCur.eventCode = EVENT_STATE_CHANGE | EVENT_FLAG_FINISH;
+ mHistoryCur.eventTag = mHistoryCur.localEventTag;
+ mHistoryCur.eventTag.uid = uid;
+ mHistoryCur.eventTag.string = name;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
* Records an event when some state flags change to true and some to false.
*/
public void recordStateChangeEvent(long elapsedRealtimeMs, long uptimeMs, int stateStartFlags,
diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
index 1494425..8fe1813 100644
--- a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
+++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
@@ -27,6 +27,7 @@
public static final String TAG = "RavenwoodEnvironment";
private static RavenwoodEnvironment sInstance = new RavenwoodEnvironment();
+ private static Workaround sWorkaround = new Workaround();
private RavenwoodEnvironment() {
if (isRunningOnRavenwood()) {
@@ -76,4 +77,30 @@
private boolean isRunningOnRavenwood$ravenwood() {
return true;
}
+
+ /**
+ * See {@link Workaround}. It's only usablke on Ravenwood.
+ */
+ public static Workaround workaround() {
+ if (getInstance().isRunningOnRavenwood()) {
+ return sWorkaround;
+ }
+ throw new IllegalStateException("Workaround can only be used on Ravenwood");
+ }
+
+ /**
+ * A set of APIs used to work around missing features on Ravenwood. Ideally, this class should
+ * be empty, and all its APIs should be able to be implemented properly.
+ */
+ public static class Workaround {
+ Workaround() {
+ }
+
+ /**
+ * @return whether the app's target SDK level is at least Q.
+ */
+ public boolean isTargetSdkAtLeastQ() {
+ return true;
+ }
+ }
}
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
index 6f1c763..8f70268 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -436,7 +436,7 @@
int result = SQLITE_OK;
if (connection->tableQuery == nullptr) {
static char const* sql =
- "SELECT COUNT(*) FROM tables_used(?) WHERE schema != 'temp' AND wr != 0";
+ "SELECT NULL FROM tables_used(?) WHERE schema != 'temp' AND wr != 0";
result = sqlite3_prepare_v2(connection->db, sql, -1, &connection->tableQuery, nullptr);
if (result != SQLITE_OK) {
ALOGE("failed to compile query table: %s",
@@ -447,25 +447,20 @@
// A temporary, to simplify the code.
sqlite3_stmt* query = connection->tableQuery;
- sqlite3_reset(query);
- sqlite3_clear_bindings(query);
result = sqlite3_bind_text(query, 1, sqlite3_sql(statement), -1, SQLITE_STATIC);
if (result != SQLITE_OK) {
ALOGE("tables bind pointer returns %s", sqlite3_errstr(result));
- return false;
}
result = sqlite3_step(query);
- if (result != SQLITE_ROW) {
- ALOGE("tables query error: %d/%s", result, sqlite3_errstr(result));
- // Make sure the query is no longer bound to the statement.
- sqlite3_clear_bindings(query);
- return false;
- }
-
- int count = sqlite3_column_int(query, 0);
- // Make sure the query is no longer bound to the statement SQL string.
+ // Make sure the query is no longer bound to the statement SQL string and
+ // that is no longer holding any table locks.
+ sqlite3_reset(query);
sqlite3_clear_bindings(query);
- return count == 0;
+
+ if (result != SQLITE_ROW && result != SQLITE_DONE) {
+ ALOGE("tables query error: %d/%s", result, sqlite3_errstr(result));
+ }
+ return result == SQLITE_DONE;
}
static jint nativeGetColumnCount(JNIEnv* env, jclass clazz, jlong connectionPtr,
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 2068bd7..3006e20 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -1411,8 +1411,10 @@
return JNI_TRUE;
}
- env->CallStaticVoidMethod(gBinderOffsets.mClass, gBinderOffsets.mTransactionCallback, getpid(),
- code, flags, err);
+ if (err == FAILED_TRANSACTION) {
+ env->CallStaticVoidMethod(gBinderOffsets.mClass, gBinderOffsets.mTransactionCallback,
+ getpid(), code, flags, err);
+ }
if (err == UNKNOWN_TRANSACTION) {
return JNI_FALSE;
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 1aa635c..e831a7d 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1008,11 +1008,11 @@
}
static void nativeSetTrustedOverlay(JNIEnv* env, jclass clazz, jlong transactionObj,
- jlong nativeObject, jboolean isTrustedOverlay) {
+ jlong nativeObject, jint trustedOverlay) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
- transaction->setTrustedOverlay(ctrl, isTrustedOverlay);
+ transaction->setTrustedOverlay(ctrl, static_cast<gui::TrustedOverlay>(trustedOverlay));
}
static void nativeSetFrameRate(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
@@ -2201,6 +2201,10 @@
return jStalledTransactionInfo;
}
+static void nativeNotifyShutdown() {
+ SurfaceComposerClient::notifyShutdown();
+}
+
// ----------------------------------------------------------------------------
SurfaceControl* android_view_SurfaceControl_getNativeSurfaceControl(JNIEnv* env,
@@ -2443,7 +2447,7 @@
(void*)nativeSetTransformHint },
{"nativeGetTransformHint", "(J)I",
(void*)nativeGetTransformHint },
- {"nativeSetTrustedOverlay", "(JJZ)V",
+ {"nativeSetTrustedOverlay", "(JJI)V",
(void*)nativeSetTrustedOverlay },
{"nativeGetLayerId", "(J)I",
(void*)nativeGetLayerId },
@@ -2476,6 +2480,8 @@
(void*) nativeGetStalledTransactionInfo },
{"nativeSetDesiredPresentTimeNanos", "(JJ)V",
(void*) nativeSetDesiredPresentTimeNanos },
+ {"nativeNotifyShutdown", "()V",
+ (void*)nativeNotifyShutdown },
// clang-format on
};
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8541704..7b9235cd 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2291,6 +2291,11 @@
<permission android:name="android.permission.THREAD_NETWORK_PRIVILEGED"
android:protectionLevel="signature|privileged" />
+ <!-- @hide Allows access to Thread network APIs or shell commands ("cmd thread_network") which
+ are only for testing. -->
+ <permission android:name="android.permission.THREAD_NETWORK_TESTING"
+ android:protectionLevel="signature" />
+
<!-- #SystemApi @hide Allows an app to bypass Private DNS.
<p>Not for use by third-party applications.
TODO: publish as system API in next API release. -->
diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml
index 9ad577a..85d34e2 100644
--- a/core/res/res/values-watch/themes_device_defaults.xml
+++ b/core/res/res/values-watch/themes_device_defaults.xml
@@ -133,6 +133,8 @@
<item name="colorControlActivated">?attr/colorControlHighlight</item>
<item name="listPreferredItemPaddingStart">?attr/dialogPreferredPadding</item>
<item name="listPreferredItemPaddingEnd">?attr/dialogPreferredPadding</item>
+ <item name="iconfactoryIconSize">@dimen/resolver_icon_size</item>
+ <item name="iconfactoryBadgeSize">@dimen/resolver_badge_size</item>
</style>
<!-- Use a dark theme for watches. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 28cd210..0706b32 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1193,13 +1193,14 @@
<!-- Allows activities to be launched on a long press on power during device setup. -->
<bool name="config_allowStartActivityForLongPressOnPowerInSetup">false</bool>
- <!-- Control the behavior when the user short presses the settings button.
- 0 - Nothing
+ <!-- Control the behavior when the user presses the settings button.
+ 0 - Launch Settings activity
1 - Launch notification panel
+ 2 - Nothing
This needs to match the constants in
com/android/server/policy/PhoneWindowManager.java
-->
- <integer name="config_shortPressOnSettingsBehavior">0</integer>
+ <integer name="config_settingsKeyBehavior">0</integer>
<!-- Control the behavior when the user short presses the power button.
0 - Nothing
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index e420ffe..04f6f52 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -309,7 +309,7 @@
fresh, it will be used as the current location by Telephony to decide whether satellite
services should be allowed.
-->
- <integer name="config_oem_enabled_satellite_location_fresh_duration">600</integer>
+ <integer name="config_oem_enabled_satellite_location_fresh_duration">300</integer>
<java-symbol type="integer" name="config_oem_enabled_satellite_location_fresh_duration" />
<!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks
@@ -384,4 +384,23 @@
<bool name="config_wait_for_device_alignment_in_demo_datagram">false</bool>
<java-symbol type="bool" name="config_wait_for_device_alignment_in_demo_datagram" />
+ <!-- The time duration in millis after which Telephony will abort the last message datagram
+ sending requests. Telephony starts a timer when receiving a last message datagram sending
+ request in either OFF, IDLE, or NOT_CONNECTED state. In NOT_CONNECTED, the duration of the
+ timer is given by this config.
+ In OFF or IDLE state, the duration of the timer is the sum of this config and the
+ config_satellite_modem_image_switching_duration_millis.
+ -->
+ <integer name="config_datagram_wait_for_connected_state_for_last_message_timeout_millis">60000</integer>
+ <java-symbol type="integer" name="config_datagram_wait_for_connected_state_for_last_message_timeout_millis" />
+
+ <!-- The time duration in millis after which Telephony will abort the last message datagram
+ sending requests and send failure response to the client that has requested sending the
+ datagrams. Telephony starts a timer after pushing down the last message datagram sending
+ request to modem. Before expiry, the timer will be stopped when Telephony receives the response
+ for the sending request from modem.
+ -->
+ <integer name="config_wait_for_datagram_sending_response_for_last_message_timeout_millis">60000</integer>
+ <java-symbol type="integer" name="config_wait_for_datagram_sending_response_for_last_message_timeout_millis" />
+
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index b885e03..a0807ca 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -72,6 +72,9 @@
<!-- The default margin used in immersive mode to capture the start of a swipe gesture from the
edge of the screen to show the system bars. -->
<dimen name="system_gestures_start_threshold">24dp</dimen>
+ <!-- The minimum swipe gesture distance for showing the system bars when in immersive mode. This
+ swipe must be within the specified system_gestures_start_threshold area. -->
+ <dimen name="system_gestures_distance_threshold">24dp</dimen>
<!-- Height of the bottom navigation bar frame; this is different than navigation_bar_height
where that is the height reported to all the other windows to resize themselves around the
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9f1b7ae..bb73934 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1802,6 +1802,7 @@
<java-symbol type="dimen" name="taskbar_frame_height" />
<java-symbol type="dimen" name="status_bar_height" />
<java-symbol type="dimen" name="display_cutout_touchable_region_size" />
+ <java-symbol type="dimen" name="system_gestures_distance_threshold" />
<java-symbol type="dimen" name="system_gestures_start_threshold" />
<java-symbol type="dimen" name="quick_qs_offset_height" />
<java-symbol type="drawable" name="ic_jog_dial_sound_off" />
@@ -1858,7 +1859,7 @@
<java-symbol type="integer" name="config_lidNavigationAccessibility" />
<java-symbol type="integer" name="config_lidOpenRotation" />
<java-symbol type="integer" name="config_longPressOnHomeBehavior" />
- <java-symbol type="integer" name="config_shortPressOnSettingsBehavior" />
+ <java-symbol type="integer" name="config_settingsKeyBehavior" />
<java-symbol type="layout" name="global_actions" />
<java-symbol type="layout" name="global_actions_item" />
<java-symbol type="layout" name="global_actions_silent_mode" />
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index e118c98d..c4695d9 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -375,6 +375,8 @@
assertEquals(3, s.getColumnInt(0));
}
+ mDatabase.execSQL("DROP TABLE t1");
+
} catch (SQLiteException e) {
allowed = false;
} finally {
diff --git a/core/tests/coretests/src/android/view/ContentRecordingSessionTest.java b/core/tests/coretests/src/android/view/ContentRecordingSessionTest.java
index 17980ac..f8800cb 100644
--- a/core/tests/coretests/src/android/view/ContentRecordingSessionTest.java
+++ b/core/tests/coretests/src/android/view/ContentRecordingSessionTest.java
@@ -18,6 +18,7 @@
import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
+import static android.view.ContentRecordingSession.TASK_ID_UNKNOWN;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
@@ -45,6 +46,7 @@
@Presubmit
public class ContentRecordingSessionTest {
private static final int DISPLAY_ID = 1;
+ private static final int TASK_ID = 123;
private static final IBinder WINDOW_TOKEN = new Binder("DisplayContentWindowToken");
@Test
@@ -65,6 +67,16 @@
ContentRecordingSession session = ContentRecordingSession.createTaskSession(WINDOW_TOKEN);
assertThat(session.getContentToRecord()).isEqualTo(RECORD_CONTENT_TASK);
assertThat(session.getTokenToRecord()).isEqualTo(WINDOW_TOKEN);
+ assertThat(session.getTaskId()).isEqualTo(TASK_ID_UNKNOWN);
+ }
+
+ @Test
+ public void testSecondaryTaskConstructor() {
+ ContentRecordingSession session =
+ ContentRecordingSession.createTaskSession(WINDOW_TOKEN, TASK_ID);
+ assertThat(session.getContentToRecord()).isEqualTo(RECORD_CONTENT_TASK);
+ assertThat(session.getTokenToRecord()).isEqualTo(WINDOW_TOKEN);
+ assertThat(session.getTaskId()).isEqualTo(TASK_ID);
}
@Test
@@ -73,6 +85,7 @@
DEFAULT_DISPLAY);
assertThat(session.getContentToRecord()).isEqualTo(RECORD_CONTENT_DISPLAY);
assertThat(session.getTokenToRecord()).isNull();
+ assertThat(session.getTaskId()).isEqualTo(TASK_ID_UNKNOWN);
}
@Test
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml
index 78c8881..297c490 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml
@@ -5,7 +5,7 @@
android:versionCode="1"
android:versionName="1.0">
- <uses-sdk android:minSdkVersion="8"
+ <uses-sdk android:minSdkVersion="21"
android:targetSdkVersion="18"/>
<application android:name="com.android.multidexlegacyandexception.TestApplication"
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/AndroidManifest.xml
index 1a60c1e..a208268 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/AndroidManifest.xml
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/AndroidManifest.xml
@@ -5,7 +5,7 @@
android:versionCode="1"
android:versionName="1.0">
- <uses-sdk android:minSdkVersion="8"
+ <uses-sdk android:minSdkVersion="21"
android:targetSdkVersion="18"/>
<application android:name=".TestApplication"
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/AndroidManifest.xml
index 35369c7..bb2a201 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/AndroidManifest.xml
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/AndroidManifest.xml
@@ -4,7 +4,7 @@
android:versionCode="1"
android:versionName="1.0" >
- <uses-sdk android:minSdkVersion="8" />
+ <uses-sdk android:minSdkVersion="21" />
<instrumentation
android:name="com.android.test.runner.MultiDexTestRunner"
android:targetPackage="com.android.multidexlegacytestapp" />
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/AndroidManifest.xml
index 1cadfcd..b96566c 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/AndroidManifest.xml
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/AndroidManifest.xml
@@ -4,7 +4,7 @@
android:versionCode="1"
android:versionName="1.0" >
- <uses-sdk android:minSdkVersion="8" />
+ <uses-sdk android:minSdkVersion="21" />
<instrumentation
android:name="com.android.multidexlegacytestapp.test2.MultiDexAndroidJUnitRunner"
android:targetPackage="com.android.multidexlegacytestapp" />
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppWithCorruptedDex/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppWithCorruptedDex/AndroidManifest.xml
index 840daab..3ad61ca 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppWithCorruptedDex/AndroidManifest.xml
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppWithCorruptedDex/AndroidManifest.xml
@@ -5,7 +5,7 @@
android:versionCode="1"
android:versionName="1.0">
- <uses-sdk android:minSdkVersion="19"
+ <uses-sdk android:minSdkVersion="21"
android:targetSdkVersion="19"/>
<application android:name="androidx.multidex.MultiDexApplication"
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests/AndroidManifest.xml
index e2fba4e..c644c36 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests/AndroidManifest.xml
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests/AndroidManifest.xml
@@ -4,7 +4,7 @@
android:versionCode="1"
android:versionName="1.0" >
- <uses-sdk android:minSdkVersion="9" />
+ <uses-sdk android:minSdkVersion="21" />
<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.android.framework.multidexlegacytestservices" />
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/AndroidManifest.xml
index 01285e7..f511c5f 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/AndroidManifest.xml
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/AndroidManifest.xml
@@ -4,7 +4,7 @@
android:versionCode="1"
android:versionName="1.0" >
- <uses-sdk android:minSdkVersion="9" />
+ <uses-sdk android:minSdkVersion="21" />
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/AndroidManifest.xml
index 8c911c4..4730243 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/AndroidManifest.xml
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/AndroidManifest.xml
@@ -5,7 +5,7 @@
android:versionCode="1"
android:versionName="1.0">
- <uses-sdk android:minSdkVersion="9"
+ <uses-sdk android:minSdkVersion="21"
android:targetSdkVersion="18"/>
<application android:name="androidx.multidex.MultiDexApplication"
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/AndroidManifest.xml
index 1817e95..0bcf9fe 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/AndroidManifest.xml
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/AndroidManifest.xml
@@ -5,7 +5,7 @@
android:versionCode="2"
android:versionName="2.0">
- <uses-sdk android:minSdkVersion="9"
+ <uses-sdk android:minSdkVersion="21"
android:targetSdkVersion="18"/>
<application android:name="androidx.multidex.MultiDexApplication"
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/AndroidManifest.xml
index c8a41bc..5b7680d 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/AndroidManifest.xml
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/AndroidManifest.xml
@@ -5,7 +5,7 @@
android:versionCode="3"
android:versionName="3.0">
- <uses-sdk android:minSdkVersion="9"
+ <uses-sdk android:minSdkVersion="21"
android:targetSdkVersion="18"/>
<application android:name="androidx.multidex.MultiDexApplication"
diff --git a/data/etc/core.protolog.pb b/data/etc/core.protolog.pb
index b41a607..ddb706e 100644
--- a/data/etc/core.protolog.pb
+++ b/data/etc/core.protolog.pb
Binary files differ
diff --git a/data/etc/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml
index 7823277..f9fb84d 100644
--- a/data/etc/preinstalled-packages-platform.xml
+++ b/data/etc/preinstalled-packages-platform.xml
@@ -134,4 +134,19 @@
<install-in-user-type package="com.android.avatarpicker">
<install-in user-type="FULL" />
</install-in-user-type>
+
+ <!-- AiLabs Warp app pre-installed in hardware/google/pixel/common/pixel-common-device.mk -->
+ <install-in-user-type package="com.google.android.apps.warp">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" />
+ </install-in-user-type>
+
+ <!-- Google Home app pre-installed on tangor devices in vendor/google/products/tangor_common.mk
+ -->
+ <install-in-user-type package="com.google.android.apps.chromecast.app">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" />
+ </install-in-user-type>
</config>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 500e9b7..80f143c 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -3223,6 +3223,36 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/TransitionController.java"
},
+ "-4546322749928357965": {
+ "message": "Registering transition player %s over %d other players",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
+ "-4250307779892136611": {
+ "message": "Registering transition player %s ",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
+ "3242771541905259983": {
+ "message": "Attempt to unregister transition player %s but it isn't registered",
+ "level": "WARN",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
+ "3691912781236221027": {
+ "message": "Unregistering active transition player %s at index=%d leaving %d in stack",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
+ "-2879980134100946679": {
+ "message": "Unregistering transition player %s at index=%d leaving %d in stack",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
"-4235778637051052061": {
"message": "Disabling player for transition #%d because display isn't enabled yet",
"level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 67f1650..08e695b 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -92,3 +92,10 @@
description: "Enables Taskbar on phones"
bug: "341784466"
}
+
+flag {
+ name: "enable_bubble_anything"
+ namespace: "multitasking"
+ description: "Enable UI affordances to put other content into a bubble"
+ bug: "342245211"
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index 35a4a62..0efdbdc 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -23,6 +23,7 @@
import android.graphics.Color
import android.graphics.drawable.Icon
import android.os.UserHandle
+import android.platform.test.flag.junit.SetFlagsRule
import android.view.IWindowManager
import android.view.WindowManager
import android.view.WindowManagerGlobal
@@ -33,6 +34,7 @@
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.protolog.common.ProtoLog
import com.android.launcher3.icons.BubbleIconFactory
+import com.android.wm.shell.Flags
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix
@@ -44,19 +46,24 @@
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
import org.junit.After
-import java.util.concurrent.Semaphore
-import java.util.concurrent.TimeUnit
-import java.util.function.Consumer
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import java.util.concurrent.Semaphore
+import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
/** Unit tests for [BubbleStackView]. */
@SmallTest
@RunWith(AndroidJUnit4::class)
class BubbleStackViewTest {
+ @get:Rule val setFlagsRule = SetFlagsRule()
+
private val context = ApplicationProvider.getApplicationContext<Context>()
private lateinit var positioner: BubblePositioner
private lateinit var iconFactory: BubbleIconFactory
@@ -66,6 +73,8 @@
private lateinit var windowManager: IWindowManager
private lateinit var bubbleTaskViewFactory: BubbleTaskViewFactory
private lateinit var bubbleData: BubbleData
+ private lateinit var bubbleStackViewManager: FakeBubbleStackViewManager
+ private var sysuiProxy = mock<SysuiProxy>()
@Before
fun setUp() {
@@ -86,7 +95,6 @@
)
)
positioner = BubblePositioner(context, windowManager)
- val bubbleStackViewManager = FakeBubbleStackViewManager()
bubbleData =
BubbleData(
context,
@@ -95,8 +103,7 @@
BubbleEducationController(context),
shellExecutor
)
-
- val sysuiProxy = mock<SysuiProxy>()
+ bubbleStackViewManager = FakeBubbleStackViewManager()
expandedViewManager = FakeBubbleExpandedViewManager()
bubbleTaskViewFactory = FakeBubbleTaskViewFactory()
bubbleStackView =
@@ -234,6 +241,115 @@
.inOrder()
}
+ @EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
+ @Test
+ fun testCreateStackView_noOverflowContents_noOverflow() {
+ bubbleStackView =
+ BubbleStackView(
+ context,
+ bubbleStackViewManager,
+ positioner,
+ bubbleData,
+ null,
+ FloatingContentCoordinator(),
+ { sysuiProxy },
+ shellExecutor
+ )
+
+ assertThat(bubbleData.overflowBubbles).isEmpty()
+ val bubbleOverflow = bubbleData.overflow
+ // Overflow shouldn't be attached
+ assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isEqualTo(-1)
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
+ @Test
+ fun testCreateStackView_hasOverflowContents_hasOverflow() {
+ // Add a bubble to the overflow
+ val bubble1 = createAndInflateChatBubble(key = "bubble1")
+ bubbleData.notificationEntryUpdated(bubble1, false, false)
+ bubbleData.dismissBubbleWithKey(bubble1.key, Bubbles.DISMISS_USER_GESTURE)
+ assertThat(bubbleData.overflowBubbles).isNotEmpty()
+
+ bubbleStackView =
+ BubbleStackView(
+ context,
+ bubbleStackViewManager,
+ positioner,
+ bubbleData,
+ null,
+ FloatingContentCoordinator(),
+ { sysuiProxy },
+ shellExecutor
+ )
+ val bubbleOverflow = bubbleData.overflow
+ assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isGreaterThan(-1)
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
+ @Test
+ fun testCreateStackView_noOverflowContents_hasOverflow() {
+ bubbleStackView =
+ BubbleStackView(
+ context,
+ bubbleStackViewManager,
+ positioner,
+ bubbleData,
+ null,
+ FloatingContentCoordinator(),
+ { sysuiProxy },
+ shellExecutor
+ )
+
+ assertThat(bubbleData.overflowBubbles).isEmpty()
+ val bubbleOverflow = bubbleData.overflow
+ assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isGreaterThan(-1)
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
+ @Test
+ fun showOverflow_true() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.showOverflow(true)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+
+ val bubbleOverflow = bubbleData.overflow
+ assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isGreaterThan(-1)
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
+ @Test
+ fun showOverflow_false() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.showOverflow(true)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ val bubbleOverflow = bubbleData.overflow
+ assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isGreaterThan(-1)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.showOverflow(false)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+
+ // The overflow should've been removed
+ assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isEqualTo(-1)
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
+ @Test
+ fun showOverflow_ignored() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.showOverflow(false)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+
+ // showOverflow should've been ignored, so the overflow would be attached
+ val bubbleOverflow = bubbleData.overflow
+ assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isGreaterThan(-1)
+ }
+
private fun createAndInflateChatBubble(key: String): Bubble {
val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
val shortcutInfo = ShortcutInfo.Builder(context, "fakeId").setIcon(icon).build()
diff --git a/libs/WindowManager/Shell/res/drawable/circular_progress.xml b/libs/WindowManager/Shell/res/drawable/circular_progress.xml
index 294b1f0..0d64527 100644
--- a/libs/WindowManager/Shell/res/drawable/circular_progress.xml
+++ b/libs/WindowManager/Shell/res/drawable/circular_progress.xml
@@ -24,7 +24,7 @@
android:toDegrees="275">
<shape
android:shape="ring"
- android:thickness="3dp"
+ android:thickness="2dp"
android:innerRadius="14dp"
android:useLevel="true">
</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_background.xml
index 5d9fe67..9566f2f 100644
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_background.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_background.xml
@@ -15,7 +15,8 @@
~ limitations under the License.
-->
<shape android:shape="rectangle"
- xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@android:color/white" />
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerLow" />
<corners android:radius="@dimen/desktop_mode_maximize_menu_corner_radius" />
</shape>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
index 4d06dd3..84e1449 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -28,13 +28,11 @@
<LinearLayout
android:id="@+id/open_menu_button"
android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:tint="?androidprv:attr/materialColorOnSurface"
- android:background="?android:selectableItemBackground"
+ android:layout_height="40dp"
android:orientation="horizontal"
android:clickable="true"
android:focusable="true"
- android:paddingStart="12dp">
+ android:layout_marginStart="12dp">
<ImageView
android:id="@+id/application_icon"
android:layout_width="@dimen/desktop_mode_caption_icon_radius"
@@ -85,8 +83,6 @@
android:layout_height="40dp"
android:layout_gravity="end"
android:layout_marginHorizontal="8dp"
- android:paddingHorizontal="5dp"
- android:paddingVertical="3dp"
android:clickable="true"
android:focusable="true"/>
@@ -97,7 +93,6 @@
android:paddingHorizontal="10dp"
android:paddingVertical="8dp"
android:layout_marginEnd="8dp"
- android:background="?android:selectableItemBackgroundBorderless"
android:contentDescription="@string/close_button_text"
android:src="@drawable/desktop_mode_header_ic_close"
android:scaleType="centerCrop"
diff --git a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
index 77507a4..cf1b894 100644
--- a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
+++ b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
@@ -16,20 +16,28 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
- <ProgressBar
- android:id="@+id/progress_bar"
- style="?android:attr/progressBarStyleHorizontal"
- android:progressDrawable="@drawable/circular_progress"
- android:layout_width="34dp"
- android:layout_height="34dp"
- android:indeterminate="false"
- android:visibility="invisible"/>
+
+ <FrameLayout
+ android:layout_width="44dp"
+ android:layout_height="40dp">
+ <ProgressBar
+ android:id="@+id/progress_bar"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:progressDrawable="@drawable/circular_progress"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:indeterminate="false"
+ android:layout_marginHorizontal="6dp"
+ android:layout_marginVertical="4dp"
+ android:visibility="invisible"/>
+ </FrameLayout>
<ImageButton
android:id="@+id/maximize_window"
- android:layout_width="34dp"
- android:layout_height="34dp"
- android:padding="5dp"
+ android:layout_width="44dp"
+ android:layout_height="40dp"
+ android:paddingHorizontal="10dp"
+ android:paddingVertical="8dp"
android:contentDescription="@string/maximize_button_text"
android:src="@drawable/decor_desktop_mode_maximize_button_dark"
android:scaleType="fitCenter" />
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 8bc3004..f27f46c 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -553,6 +553,25 @@
<!-- The corner radius of a task that was dragged from fullscreen. -->
<dimen name="desktop_mode_dragged_task_radius">28dp</dimen>
+ <!-- The corner radius of the app chip, maximize and close button's ripple drawable -->
+ <dimen name="desktop_mode_header_buttons_ripple_radius">16dp</dimen>
+ <!-- The vertical inset to apply to the app chip's ripple drawable -->
+ <dimen name="desktop_mode_header_app_chip_ripple_inset_vertical">4dp</dimen>
+
+ <!-- The corner radius of the maximize button's ripple drawable -->
+ <dimen name="desktop_mode_header_maximize_ripple_radius">18dp</dimen>
+ <!-- The vertical inset to apply to the maximize button's ripple drawable -->
+ <dimen name="desktop_mode_header_maximize_ripple_inset_vertical">4dp</dimen>
+ <!-- The horizontal inset to apply to the maximize button's ripple drawable -->
+ <dimen name="desktop_mode_header_maximize_ripple_inset_horizontal">6dp</dimen>
+
+ <!-- The corner radius of the close button's ripple drawable -->
+ <dimen name="desktop_mode_header_close_ripple_radius">18dp</dimen>
+ <!-- The vertical inset to apply to the close button's ripple drawable -->
+ <dimen name="desktop_mode_header_close_ripple_inset_vertical">4dp</dimen>
+ <!-- The horizontal inset to apply to the close button's ripple drawable -->
+ <dimen name="desktop_mode_header_close_ripple_inset_horizontal">6dp</dimen>
+
<!-- The acceptable area ratio of fg icon area/bg icon area, i.e. (72 x 72) / (108 x 108) -->
<item type="dimen" format="float" name="splash_icon_enlarge_foreground_threshold">0.44</item>
<!-- Scaling factor applied to splash icons without provided background i.e. (192 / 160) -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 3244837..f2095b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -552,10 +552,12 @@
// Notify the compat UI if the listener or task info changed.
notifyCompatUI(taskInfo, newListener);
}
- if (data.getTaskInfo().getWindowingMode() != taskInfo.getWindowingMode()) {
- // Notify the recent tasks when a task changes windowing modes
+ final boolean windowModeChanged =
+ data.getTaskInfo().getWindowingMode() != taskInfo.getWindowingMode();
+ final boolean visibilityChanged = data.getTaskInfo().isVisible != taskInfo.isVisible;
+ if (windowModeChanged || visibilityChanged) {
mRecentTasks.ifPresent(recentTasks ->
- recentTasks.onTaskWindowingModeChanged(taskInfo));
+ recentTasks.onTaskRunningInfoChanged(taskInfo));
}
// TODO (b/207687679): Remove check for HOME once bug is fixed
final boolean isFocusedOrHome = taskInfo.isFocused
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
index 7749394..d754d04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
@@ -19,8 +19,6 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
-import static com.android.wm.shell.back.BackAnimationConstants.UPDATE_SYSUI_FLAGS_THRESHOLD;
-
import android.annotation.NonNull;
import android.graphics.Color;
import android.graphics.Rect;
@@ -45,6 +43,7 @@
private boolean mIsRequestingStatusBarAppearance;
private boolean mBackgroundIsDark;
private Rect mStartBounds;
+ private int mStatusbarHeight;
public BackAnimationBackground(RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
@@ -56,9 +55,10 @@
* @param startRect The start bounds of the closing target.
* @param color The background color.
* @param transaction The animation transaction.
+ * @param statusbarHeight The height of the statusbar (in px).
*/
- public void ensureBackground(
- Rect startRect, int color, @NonNull SurfaceControl.Transaction transaction) {
+ public void ensureBackground(Rect startRect, int color,
+ @NonNull SurfaceControl.Transaction transaction, int statusbarHeight) {
if (mBackgroundSurface != null) {
return;
}
@@ -80,6 +80,7 @@
.show(mBackgroundSurface);
mStartBounds = startRect;
mIsRequestingStatusBarAppearance = false;
+ mStatusbarHeight = statusbarHeight;
}
/**
@@ -111,14 +112,14 @@
/**
* Update back animation background with for the progress.
*
- * @param progress Progress value from {@link android.window.BackProgressAnimator}
+ * @param top The top coordinate of the closing target
*/
- public void onBackProgressed(float progress) {
+ public void customizeStatusBarAppearance(int top) {
if (mCustomizer == null || mStartBounds.isEmpty()) {
return;
}
- final boolean shouldCustomizeSystemBar = progress > UPDATE_SYSUI_FLAGS_THRESHOLD;
+ final boolean shouldCustomizeSystemBar = top > mStatusbarHeight / 2;
if (shouldCustomizeSystemBar == mIsRequestingStatusBarAppearance) {
return;
}
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 7c0837e..ee740fb 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
@@ -46,6 +46,7 @@
import com.android.internal.dynamicanimation.animation.SpringForce
import com.android.internal.jank.Cuj
import com.android.internal.policy.ScreenDecorationsUtils
+import com.android.internal.policy.SystemBarUtils
import com.android.internal.protolog.common.ProtoLog
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -76,6 +77,7 @@
private val tempRectF = RectF()
private var cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
+ private var statusbarHeight = SystemBarUtils.getStatusBarHeight(context)
private val backAnimationRunner =
BackAnimationRunner(Callback(), Runner(), context, Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY)
@@ -129,6 +131,11 @@
abstract fun preparePreCommitEnteringRectMovement()
/**
+ * Subclasses must provide a duration (in ms) for the post-commit part of the animation
+ */
+ abstract fun getPostCommitAnimationDuration(): Long
+
+ /**
* Returns a base transformation to apply to the entering target during pre-commit. The system
* will apply the default animation on top of it.
*/
@@ -137,6 +144,7 @@
override fun onConfigurationChanged(newConfiguration: Configuration) {
cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
+ statusbarHeight = SystemBarUtils.getStatusBarHeight(context)
}
override fun getRunner() = backAnimationRunner
@@ -182,7 +190,8 @@
background.ensureBackground(
closingTarget!!.windowConfiguration.bounds,
getBackgroundColor(),
- transaction
+ transaction,
+ statusbarHeight
)
ensureScrimLayer()
if (isLetterboxed && enteringHasSameLetterbox) {
@@ -203,7 +212,6 @@
private fun onGestureProgress(backEvent: BackEvent) {
val progress = gestureInterpolator.getInterpolation(backEvent.progress)
- background.onBackProgressed(progress)
currentClosingRect.setInterpolatedRectF(startClosingRect, targetClosingRect, progress)
val yOffset = getYOffset(currentClosingRect, backEvent.touchY)
currentClosingRect.offset(0f, yOffset)
@@ -218,6 +226,7 @@
enteringTransformation
)
applyTransaction()
+ background.customizeStatusBarAppearance(currentClosingRect.top.toInt())
}
private fun getYOffset(centeredRect: RectF, touchY: Float): Float {
@@ -255,7 +264,8 @@
.setSpring(postCommitFlingSpring)
flingAnimation.start()
- val valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(POST_COMMIT_DURATION)
+ val valueAnimator =
+ ValueAnimator.ofFloat(1f, 0f).setDuration(getPostCommitAnimationDuration())
valueAnimator.addUpdateListener { animation: ValueAnimator ->
val progress = animation.animatedFraction
onPostCommitProgress(progress)
@@ -518,7 +528,6 @@
internal const val MAX_SCALE = 0.9f
private const val MAX_SCRIM_ALPHA_DARK = 0.8f
private const val MAX_SCRIM_ALPHA_LIGHT = 0.2f
- private const val POST_COMMIT_DURATION = 300L
private const val SPRING_SCALE = 100f
private const val MAX_FLING_SCALE = 0.6f
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index ee898a7..381914a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -48,6 +48,7 @@
import android.window.IOnBackInvokedCallback;
import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.policy.SystemBarUtils;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
@@ -82,6 +83,7 @@
private final Rect mStartTaskRect = new Rect();
private float mCornerRadius;
+ private int mStatusbarHeight;
// The closing window properties.
private final Rect mClosingStartRect = new Rect();
@@ -114,16 +116,21 @@
@Inject
public CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
- mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
mBackAnimationRunner = new BackAnimationRunner(
new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_TASK);
mBackground = background;
mContext = context;
+ loadResources();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
+ loadResources();
+ }
+
+ private void loadResources() {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext);
+ mStatusbarHeight = SystemBarUtils.getStatusBarHeight(mContext);
}
private static float mapRange(float value, float min, float max) {
@@ -149,7 +156,7 @@
// Draw background.
mBackground.ensureBackground(mClosingTarget.windowConfiguration.getBounds(),
- BACKGROUNDCOLOR, mTransaction);
+ BACKGROUNDCOLOR, mTransaction, mStatusbarHeight);
mInterWindowMargin = mContext.getResources()
.getDimension(R.dimen.cross_task_back_inter_window_margin);
mVerticalMargin = mContext.getResources()
@@ -201,7 +208,7 @@
applyTransform(mEnteringTarget.leash, mEnteringCurrentRect, mCornerRadius);
applyTransaction();
- mBackground.onBackProgressed(progress);
+ mBackground.customizeStatusBarAppearance((int) scaledTop);
}
private void updatePostCommitClosingAnimation(float progress) {
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 ab359bd..c4aafea 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
@@ -33,6 +33,8 @@
import com.android.wm.shell.protolog.ShellProtoLogGroup
import com.android.wm.shell.shared.annotations.ShellMainThread
import javax.inject.Inject
+import kotlin.math.max
+import kotlin.math.min
/** Class that handles customized predictive cross activity back animations. */
@ShellMainThread
@@ -96,6 +98,12 @@
targetEnteringRect.set(startClosingRect)
}
+ override fun getPostCommitAnimationDuration(): Long {
+ return min(
+ MAX_POST_COMMIT_ANIM_DURATION, max(closeAnimation!!.duration, enterAnimation!!.duration)
+ )
+ }
+
override fun getPreCommitEnteringBaseTransformation(progress: Float): Transformation {
gestureProgress = progress
transformation.clear()
@@ -107,9 +115,9 @@
super.startBackAnimation(backMotionEvent)
if (
closeAnimation == null ||
- enterAnimation == null ||
- closingTarget == null ||
- enteringTarget == null
+ enterAnimation == null ||
+ closingTarget == null ||
+ enteringTarget == null
) {
ProtoLog.d(
ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW,
@@ -125,10 +133,13 @@
super.onPostCommitProgress(linearProgress)
if (closingTarget == null || enteringTarget == null) return
- // TODO: Should we use the duration from the custom xml spec for the post-commit animation?
- applyTransform(closingTarget!!.leash, currentClosingRect, linearProgress, closeAnimation!!)
- val enteringProgress =
- MathUtils.lerp(gestureProgress * PRE_COMMIT_MAX_PROGRESS, 1f, linearProgress)
+ val closingProgress = closeAnimation!!.getPostCommitProgress(linearProgress)
+ applyTransform(closingTarget!!.leash, currentClosingRect, closingProgress, closeAnimation!!)
+ val enteringProgress = MathUtils.lerp(
+ gestureProgress * PRE_COMMIT_MAX_PROGRESS,
+ 1f,
+ enterAnimation!!.getPostCommitProgress(linearProgress)
+ )
applyTransform(
enteringTarget!!.leash,
currentEnteringRect,
@@ -175,6 +186,19 @@
return false
}
+ private fun Animation.getPostCommitProgress(linearProgress: Float): Float {
+ return when (duration) {
+ 0L -> 1f
+ else -> min(
+ 1f,
+ getPostCommitAnimationDuration() / min(
+ MAX_POST_COMMIT_ANIM_DURATION,
+ duration
+ ).toFloat() * linearProgress
+ )
+ }
+ }
+
class AnimationLoadResult {
var closeAnimation: Animation? = null
var enterAnimation: Animation? = null
@@ -183,6 +207,7 @@
companion object {
private const val PRE_COMMIT_MAX_PROGRESS = 0.2f
+ private const val MAX_POST_COMMIT_ANIM_DURATION = 2000L
}
}
@@ -226,7 +251,7 @@
// Try to get animation from Activity#overrideActivityTransition
if (
enterAnimation && animationInfo.customEnterAnim != 0 ||
- !enterAnimation && animationInfo.customExitAnim != 0
+ !enterAnimation && animationInfo.customExitAnim != 0
) {
a =
transitionAnimation.loadAppTransitionAnimation(
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 9f07e5b..44752fe 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
@@ -71,6 +71,8 @@
targetEnteringRect.scaleCentered(MAX_SCALE)
}
+ override fun getPostCommitAnimationDuration() = POST_COMMIT_DURATION
+
override fun onGestureCommitted(velocity: Float) {
// We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
// coordinate of the gesture driven phase. Let's update the start and target rects and kick
@@ -93,4 +95,9 @@
applyTransform(enteringTarget?.leash, currentEnteringRect, 1f)
applyTransaction()
}
+
+
+ companion object {
+ private const val POST_COMMIT_DURATION = 300L
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 42a4ab2..317e00a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1772,7 +1772,7 @@
if (groupKey == null) {
return bubbleChildren;
}
- for (Bubble bubble : mBubbleData.getActiveBubbles()) {
+ for (Bubble bubble : mBubbleData.getBubbles()) {
if (bubble.getGroupKey() != null && groupKey.equals(bubble.getGroupKey())) {
bubbleChildren.add(bubble);
}
@@ -2228,6 +2228,7 @@
pw.print(prefix); pw.println(" currentUserId= " + mCurrentUserId);
pw.print(prefix); pw.println(" isStatusBarShade= " + mIsStatusBarShade);
pw.print(prefix); pw.println(" isShowingAsBubbleBar= " + isShowingAsBubbleBar());
+ pw.print(prefix); pw.println(" isImeVisible= " + mBubblePositioner.isImeVisible());
pw.println();
mBubbleData.dump(pw);
@@ -2730,7 +2731,7 @@
public boolean canShowBubbleNotification() {
// in bubble bar mode, when the IME is visible we can't animate new bubbles.
if (BubbleController.this.isShowingAsBubbleBar()) {
- return !BubbleController.this.mBubblePositioner.getIsImeVisible();
+ return !BubbleController.this.mBubblePositioner.isImeVisible();
}
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 26483c8..874102c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -339,11 +339,6 @@
return mOverflow;
}
- /** Return a read-only current active bubble lists. */
- public List<Bubble> getActiveBubbles() {
- return Collections.unmodifiableList(mBubbles);
- }
-
public void setExpanded(boolean expanded) {
setExpandedInternal(expanded);
dispatchPendingChanges();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 1e482ca..2382545 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -325,7 +325,7 @@
}
/** Returns whether the IME is visible. */
- public boolean getIsImeVisible() {
+ public boolean isImeVisible() {
return mImeVisible;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 69bf5fd..fac9bf6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -344,7 +344,7 @@
pw.println("Expanded bubble state:");
pw.println(" expandedBubbleKey: " + mExpandedBubble.getKey());
- final BubbleExpandedView expandedView = mExpandedBubble.getExpandedView();
+ final BubbleExpandedView expandedView = getExpandedView();
if (expandedView != null) {
pw.println(" expandedViewVis: " + expandedView.getVisibility());
@@ -815,10 +815,11 @@
private float getScrimAlphaForDrag(float dragAmount) {
// dragAmount should be negative as we allow scroll up only
- if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ BubbleExpandedView expandedView = getExpandedView();
+ if (expandedView != null) {
float alphaRange = BUBBLE_EXPANDED_SCRIM_ALPHA - MIN_SCRIM_ALPHA_FOR_DRAG;
- int dragMax = mExpandedBubble.getExpandedView().getContentHeight();
+ int dragMax = expandedView.getContentHeight();
float dragFraction = dragAmount / dragMax;
return Math.max(BUBBLE_EXPANDED_SCRIM_ALPHA - alphaRange * dragFraction,
@@ -1120,33 +1121,35 @@
mExpandedViewAlphaAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
- if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ BubbleExpandedView expandedView = getExpandedView();
+ if (expandedView != null) {
// We need to be Z ordered on top in order for alpha animations to work.
- mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(true);
- mExpandedBubble.getExpandedView().setAnimating(true);
+ expandedView.setSurfaceZOrderedOnTop(true);
+ expandedView.setAnimating(true);
mExpandedViewContainer.setVisibility(VISIBLE);
}
}
@Override
public void onAnimationEnd(Animator animation) {
- if (mExpandedBubble != null
- && mExpandedBubble.getExpandedView() != null
+ BubbleExpandedView expandedView = getExpandedView();
+ if (expandedView != null
// The surface needs to be Z ordered on top for alpha values to work on the
// TaskView, and if we're temporarily hidden, we are still on the screen
// with alpha = 0f until we animate back. Stay Z ordered on top so the alpha
// = 0f remains in effect.
&& !mExpandedViewTemporarilyHidden) {
- mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
- mExpandedBubble.getExpandedView().setAnimating(false);
+ expandedView.setSurfaceZOrderedOnTop(false);
+ expandedView.setAnimating(false);
}
}
});
mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> {
- if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ BubbleExpandedView expandedView = getExpandedView();
+ if (expandedView != null) {
float alpha = (float) valueAnimator.getAnimatedValue();
- mExpandedBubble.getExpandedView().setContentAlpha(alpha);
- mExpandedBubble.getExpandedView().setBackgroundAlpha(alpha);
+ expandedView.setContentAlpha(alpha);
+ expandedView.setBackgroundAlpha(alpha);
}
});
@@ -1390,7 +1393,7 @@
}
final boolean seen = getPrefBoolean(ManageEducationView.PREF_MANAGED_EDUCATION);
final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext))
- && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null;
+ && getExpandedView() != null;
ProtoLog.d(WM_SHELL_BUBBLES, "Show manage edu=%b", shouldShow);
if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) {
Log.w(TAG, "Want to show manage edu, but it is forced hidden");
@@ -1417,9 +1420,9 @@
* Show manage education if was not showing before.
*/
private void showManageEdu() {
- if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) return;
- mManageEduView.show(mExpandedBubble.getExpandedView(),
- mStackAnimationController.isStackOnLeftSide());
+ BubbleExpandedView expandedView = getExpandedView();
+ if (expandedView == null) return;
+ mManageEduView.show(expandedView, mStackAnimationController.isStackOnLeftSide());
}
@VisibleForTesting
@@ -1931,6 +1934,11 @@
return mExpandedBubble;
}
+ @Nullable
+ private BubbleExpandedView getExpandedView() {
+ return mExpandedBubble != null ? mExpandedBubble.getExpandedView() : null;
+ }
+
// via BubbleData.Listener
@SuppressLint("ClickableViewAccessibility")
void addBubble(Bubble bubble) {
@@ -2110,13 +2118,11 @@
// If we're expanded, screenshot the currently expanded bubble (before expanding the newly
// selected bubble) so we can animate it out.
- if (mIsExpanded && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null
- && !mExpandedViewTemporarilyHidden) {
- if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- // Before screenshotting, have the real TaskView show on top of other surfaces
- // so that the screenshot doesn't flicker on top of it.
- mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(true);
- }
+ BubbleExpandedView expandedView = getExpandedView();
+ if (mIsExpanded && expandedView != null && !mExpandedViewTemporarilyHidden) {
+ // Before screenshotting, have the real TaskView show on top of other surfaces
+ // so that the screenshot doesn't flicker on top of it.
+ expandedView.setSurfaceZOrderedOnTop(true);
try {
screenshotAnimatingOutBubbleIntoSurface((success) -> {
@@ -2136,7 +2142,7 @@
private void showNewlySelectedBubble(BubbleViewProvider bubbleToSelect) {
final BubbleViewProvider previouslySelected = mExpandedBubble;
mExpandedBubble = bubbleToSelect;
- mExpandedViewAnimationController.setExpandedView(mExpandedBubble.getExpandedView());
+ mExpandedViewAnimationController.setExpandedView(getExpandedView());
if (mIsExpanded) {
hideCurrentInputMethod();
@@ -2445,8 +2451,7 @@
mBubbleContainer.animate().translationX(0).start();
}
mExpandedAnimationController.expandFromStack(() -> {
- if (mIsExpanded && mExpandedBubble != null
- && mExpandedBubble.getExpandedView() != null) {
+ if (mIsExpanded && getExpandedView() != null) {
maybeShowManageEdu();
}
updateOverflowDotVisibility(true /* expanding */);
@@ -2509,13 +2514,14 @@
}
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
- if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- mExpandedBubble.getExpandedView().setContentAlpha(0f);
- mExpandedBubble.getExpandedView().setBackgroundAlpha(0f);
+ BubbleExpandedView expandedView = getExpandedView();
+ if (expandedView != null) {
+ expandedView.setContentAlpha(0f);
+ expandedView.setBackgroundAlpha(0f);
// We'll be starting the alpha animation after a slight delay, so set this flag early
// here.
- mExpandedBubble.getExpandedView().setAnimating(true);
+ expandedView.setAnimating(true);
}
mDelayedAnimation = () -> {
@@ -2545,10 +2551,9 @@
.withEndActions(() -> {
mExpandedViewContainer.setAnimationMatrix(null);
afterExpandedViewAnimation();
- if (mExpandedBubble != null
- && mExpandedBubble.getExpandedView() != null) {
- mExpandedBubble.getExpandedView()
- .setSurfaceZOrderedOnTop(false);
+ BubbleExpandedView expView = getExpandedView();
+ if (expView != null) {
+ expView.setSurfaceZOrderedOnTop(false);
}
})
.start();
@@ -2613,12 +2618,13 @@
};
mExpandedViewAnimationController.animateCollapse(collapseBackToStack, after,
collapsePosition);
- if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ BubbleExpandedView expandedView = getExpandedView();
+ if (expandedView != null) {
// When the animation completes, we should no longer be showing the content.
// This won't actually update content visibility immediately, if we are currently
// animating. But updates the internal state for the content to be hidden after
// animation completes.
- mExpandedBubble.getExpandedView().setContentVisibility(false);
+ expandedView.setContentVisibility(false);
}
}
@@ -2710,10 +2716,10 @@
// expanded view animation might not actually set the z ordering for the
// expanded view correctly, because the view may still be temporarily
// hidden. So set it again here.
- BubbleExpandedView bev = mExpandedBubble.getExpandedView();
- if (bev != null) {
- mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
- mExpandedBubble.getExpandedView().setAnimating(false);
+ BubbleExpandedView expandedView = getExpandedView();
+ if (expandedView != null) {
+ expandedView.setSurfaceZOrderedOnTop(false);
+ expandedView.setAnimating(false);
}
})
.start();
@@ -2785,13 +2791,13 @@
if (mIsExpanded) {
mExpandedViewAnimationController.animateForImeVisibilityChange(visible);
- if (mPositioner.showBubblesVertically()
- && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ BubbleExpandedView expandedView = getExpandedView();
+ if (mPositioner.showBubblesVertically() && expandedView != null) {
float selectedY = mPositioner.getExpandedBubbleXY(getState().selectedIndex,
getState()).y;
float newExpandedViewTop = mPositioner.getExpandedViewY(mExpandedBubble, selectedY);
- mExpandedBubble.getExpandedView().setImeVisible(visible);
- if (!mExpandedBubble.getExpandedView().isUsingMaxHeight()) {
+ expandedView.setImeVisible(visible);
+ if (!expandedView.isUsingMaxHeight()) {
mExpandedViewContainer.animate().translationY(newExpandedViewTop);
}
List<Animator> animList = new ArrayList<>();
@@ -3148,7 +3154,8 @@
// This should not happen, since the manage menu is only visible when there's an expanded
// bubble. If we end up in this state, just hide the menu immediately.
- if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
+ BubbleExpandedView expandedView = getExpandedView();
+ if (expandedView == null) {
mManageMenu.setVisibility(View.INVISIBLE);
mManageMenuScrim.setVisibility(INVISIBLE);
mSysuiProxyProvider.getSysuiProxy().onManageMenuExpandChanged(false /* show */);
@@ -3194,8 +3201,8 @@
}
}
- if (mExpandedBubble.getExpandedView().getTaskView() != null) {
- mExpandedBubble.getExpandedView().getTaskView().setObscuredTouchRect(mShowingManage
+ if (expandedView.getTaskView() != null) {
+ expandedView.getTaskView().setObscuredTouchRect(mShowingManage
? new Rect(0, 0, getWidth(), getHeight())
: null);
}
@@ -3205,8 +3212,8 @@
// When the menu is open, it should be at these coordinates. The menu pops out to the right
// in LTR and to the left in RTL.
- mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect);
- final float margin = mExpandedBubble.getExpandedView().getManageButtonMargin();
+ expandedView.getManageButtonBoundsOnScreen(mTempRect);
+ final float margin = expandedView.getManageButtonMargin();
final float targetX = isLtr
? mTempRect.left - margin
: mTempRect.right + margin - mManageMenu.getWidth();
@@ -3230,9 +3237,10 @@
.withEndActions(() -> {
View child = mManageMenu.getChildAt(0);
child.requestAccessibilityFocus();
- if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ BubbleExpandedView expView = getExpandedView();
+ if (expView != null) {
// Update the AV's obscured touchable region for the new state.
- mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
+ expView.updateObscuredTouchableRegion();
}
})
.start();
@@ -3247,9 +3255,10 @@
.spring(DynamicAnimation.TRANSLATION_Y, targetY + menuHeight / 4f)
.withEndActions(() -> {
mManageMenu.setVisibility(View.INVISIBLE);
- if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ BubbleExpandedView expView = getExpandedView();
+ if (expView != null) {
// Update the AV's obscured touchable region for the new state.
- mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
+ expView.updateObscuredTouchableRegion();
}
})
.start();
@@ -3276,9 +3285,8 @@
private void updateExpandedBubble() {
mExpandedViewContainer.removeAllViews();
- if (mIsExpanded && mExpandedBubble != null
- && mExpandedBubble.getExpandedView() != null) {
- BubbleExpandedView bev = mExpandedBubble.getExpandedView();
+ BubbleExpandedView bev = getExpandedView();
+ if (mIsExpanded && bev != null) {
bev.setContentVisibility(false);
bev.setAnimating(!mIsExpansionAnimating);
mExpandedViewContainerMatrix.setScaleX(0f);
@@ -3306,9 +3314,8 @@
}
private void updateManageButtonListener() {
- if (mIsExpanded && mExpandedBubble != null
- && mExpandedBubble.getExpandedView() != null) {
- BubbleExpandedView bev = mExpandedBubble.getExpandedView();
+ BubbleExpandedView bev = getExpandedView();
+ if (mIsExpanded && bev != null) {
bev.setManageClickListener((view) -> {
showManageMenu(true /* show */);
});
@@ -3325,14 +3332,13 @@
* expanded bubble.
*/
private void screenshotAnimatingOutBubbleIntoSurface(Consumer<Boolean> onComplete) {
- if (!mIsExpanded || mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
+ final BubbleExpandedView animatingOutExpandedView = getExpandedView();
+ if (!mIsExpanded || animatingOutExpandedView == null) {
// You can't animate null.
onComplete.accept(false);
return;
}
- final BubbleExpandedView animatingOutExpandedView = mExpandedBubble.getExpandedView();
-
// Release the previous screenshot if it hasn't been released already.
if (mAnimatingOutBubbleBuffer != null) {
releaseAnimatingOutBubbleBuffer();
@@ -3364,8 +3370,7 @@
mAnimatingOutSurfaceContainer.setTranslationX(translationX);
mAnimatingOutSurfaceContainer.setTranslationY(0);
- final int[] taskViewLocation =
- mExpandedBubble.getExpandedView().getTaskViewLocationOnScreen();
+ final int[] taskViewLocation = animatingOutExpandedView.getTaskViewLocationOnScreen();
final int[] surfaceViewLocation = mAnimatingOutSurfaceView.getLocationOnScreen();
// Translate the surface to overlap the real TaskView.
@@ -3427,15 +3432,15 @@
int[] paddings = mPositioner.getExpandedViewContainerPadding(
mStackAnimationController.isStackOnLeftSide(), isOverflowExpanded);
mExpandedViewContainer.setPadding(paddings[0], paddings[1], paddings[2], paddings[3]);
- if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ BubbleExpandedView expandedView = getExpandedView();
+ if (expandedView != null) {
PointF p = mPositioner.getExpandedBubbleXY(getBubbleIndex(mExpandedBubble),
getState());
mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY(mExpandedBubble,
mPositioner.showBubblesVertically() ? p.y : p.x));
mExpandedViewContainer.setTranslationX(0f);
- mExpandedBubble.getExpandedView().updateTaskViewContentWidth();
- mExpandedBubble.getExpandedView().updateView(
- mExpandedViewContainer.getLocationOnScreen());
+ expandedView.updateTaskViewContentWidth();
+ expandedView.updateView(mExpandedViewContainer.getLocationOnScreen());
updatePointerPosition(false /* forIme */);
}
@@ -3508,7 +3513,8 @@
* the pointer is animated to the location.
*/
private void updatePointerPosition(boolean forIme) {
- if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
+ BubbleExpandedView expandedView = getExpandedView();
+ if (mExpandedBubble == null || expandedView == null) {
return;
}
int index = getBubbleIndex(mExpandedBubble);
@@ -3519,7 +3525,7 @@
float bubblePosition = mPositioner.showBubblesVertically()
? position.y
: position.x;
- mExpandedBubble.getExpandedView().setPointerPosition(bubblePosition,
+ expandedView.setPointerPosition(bubblePosition,
mStackOnLeftOrWillBe, forIme /* animate */);
}
@@ -3541,7 +3547,7 @@
*/
int getBubbleIndex(@Nullable BubbleViewProvider provider) {
if (provider == null) {
- return 0;
+ return -1;
}
return mBubbleContainer.indexOfChild(provider.getIconView());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index 95d4714..109868d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -20,9 +20,7 @@
import com.android.wm.shell.protolog.ShellProtoLogGroup
import com.android.wm.shell.util.KtProtoLog
-/**
- * Event logger for logging desktop mode session events
- */
+/** Event logger for logging desktop mode session events */
class DesktopModeEventLogger {
/**
* Logs the enter of desktop mode having session id [sessionId] and the reason [enterReason] for
@@ -32,13 +30,16 @@
KtProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging session enter, session: %s reason: %s",
- sessionId, enterReason.name
+ sessionId,
+ enterReason.name
)
- FrameworkStatsLog.write(DESKTOP_MODE_ATOM_ID,
+ FrameworkStatsLog.write(
+ DESKTOP_MODE_ATOM_ID,
/* event */ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER,
/* enterReason */ enterReason.reason,
/* exitReason */ 0,
- /* session_id */ sessionId)
+ /* session_id */ sessionId
+ )
}
/**
@@ -49,13 +50,16 @@
KtProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging session exit, session: %s reason: %s",
- sessionId, exitReason.name
+ sessionId,
+ exitReason.name
)
- FrameworkStatsLog.write(DESKTOP_MODE_ATOM_ID,
+ FrameworkStatsLog.write(
+ DESKTOP_MODE_ATOM_ID,
/* event */ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__EXIT,
/* enterReason */ 0,
/* exitReason */ exitReason.reason,
- /* session_id */ sessionId)
+ /* session_id */ sessionId
+ )
}
/**
@@ -66,9 +70,11 @@
KtProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging task added, session: %s taskId: %s",
- sessionId, taskUpdate.instanceId
+ sessionId,
+ taskUpdate.instanceId
)
- FrameworkStatsLog.write(DESKTOP_MODE_TASK_UPDATE_ATOM_ID,
+ FrameworkStatsLog.write(
+ DESKTOP_MODE_TASK_UPDATE_ATOM_ID,
/* task_event */
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED,
/* instance_id */
@@ -84,7 +90,8 @@
/* task_y */
taskUpdate.taskY,
/* session_id */
- sessionId)
+ sessionId
+ )
}
/**
@@ -95,9 +102,11 @@
KtProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging task remove, session: %s taskId: %s",
- sessionId, taskUpdate.instanceId
+ sessionId,
+ taskUpdate.instanceId
)
- FrameworkStatsLog.write(DESKTOP_MODE_TASK_UPDATE_ATOM_ID,
+ FrameworkStatsLog.write(
+ DESKTOP_MODE_TASK_UPDATE_ATOM_ID,
/* task_event */
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED,
/* instance_id */
@@ -113,7 +122,8 @@
/* task_y */
taskUpdate.taskY,
/* session_id */
- sessionId)
+ sessionId
+ )
}
/**
@@ -124,9 +134,11 @@
KtProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging task info changed, session: %s taskId: %s",
- sessionId, taskUpdate.instanceId
+ sessionId,
+ taskUpdate.instanceId
)
- FrameworkStatsLog.write(DESKTOP_MODE_TASK_UPDATE_ATOM_ID,
+ FrameworkStatsLog.write(
+ DESKTOP_MODE_TASK_UPDATE_ATOM_ID,
/* task_event */
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
/* instance_id */
@@ -142,7 +154,8 @@
/* task_y */
taskUpdate.taskY,
/* session_id */
- sessionId)
+ sessionId
+ )
}
companion object {
@@ -160,12 +173,8 @@
* stats/atoms/desktopmode/desktopmode_extensions_atoms.proto
*/
enum class EnterReason(val reason: Int) {
- UNKNOWN_ENTER(
- FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__UNKNOWN_ENTER
- ),
- OVERVIEW(
- FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__OVERVIEW
- ),
+ UNKNOWN_ENTER(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__UNKNOWN_ENTER),
+ OVERVIEW(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__OVERVIEW),
APP_HANDLE_DRAG(
FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__APP_HANDLE_DRAG
),
@@ -178,9 +187,7 @@
KEYBOARD_SHORTCUT_ENTER(
FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__KEYBOARD_SHORTCUT_ENTER
),
- SCREEN_ON(
- FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__SCREEN_ON
- );
+ SCREEN_ON(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__SCREEN_ON)
}
/**
@@ -188,12 +195,8 @@
* stats/atoms/desktopmode/desktopmode_extensions_atoms.proto
*/
enum class ExitReason(val reason: Int) {
- UNKNOWN_EXIT(
- FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__UNKNOWN_EXIT
- ),
- DRAG_TO_EXIT(
- FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__DRAG_TO_EXIT
- ),
+ UNKNOWN_EXIT(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__UNKNOWN_EXIT),
+ DRAG_TO_EXIT(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__DRAG_TO_EXIT),
APP_HANDLE_MENU_BUTTON_EXIT(
FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__APP_HANDLE_MENU_BUTTON_EXIT
),
@@ -203,16 +206,12 @@
RETURN_HOME_OR_OVERVIEW(
FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME
),
- TASK_FINISHED(
- FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__TASK_FINISHED
- ),
- SCREEN_OFF(
- FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__SCREEN_OFF
- )
+ TASK_FINISHED(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__TASK_FINISHED),
+ SCREEN_OFF(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__SCREEN_OFF)
}
private const val DESKTOP_MODE_ATOM_ID = FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED
private const val DESKTOP_MODE_TASK_UPDATE_ATOM_ID =
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index 0b7a3e8..5d8e340 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -60,8 +60,9 @@
private val idSequence: InstanceIdSequence by lazy { InstanceIdSequence(Int.MAX_VALUE) }
init {
- if (Transitions.ENABLE_SHELL_TRANSITIONS &&
- DesktopModeStatus.canEnterDesktopMode(context)) {
+ if (
+ Transitions.ENABLE_SHELL_TRANSITIONS && DesktopModeStatus.canEnterDesktopMode(context)
+ ) {
shellInit.addInitCallback(this::onInit, this)
}
}
@@ -350,4 +351,4 @@
return this.type == WindowManager.TRANSIT_TO_FRONT &&
this.flags == WindowManager.TRANSIT_FLAG_IS_RECENTS
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
index f1a475a..bc27f34 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -20,9 +20,7 @@
import com.android.wm.shell.sysui.ShellCommandHandler
import java.io.PrintWriter
-/**
- * Handles the shell commands for the DesktopTasksController.
- */
+/** Handles the shell commands for the DesktopTasksController. */
class DesktopModeShellCommandHandler(private val controller: DesktopTasksController) :
ShellCommandHandler.ShellCommandActionHandler {
@@ -58,12 +56,13 @@
return false
}
- val taskId = try {
- args[1].toInt()
- } catch (e: NumberFormatException) {
- pw.println("Error: task id should be an integer")
- return false
- }
+ val taskId =
+ try {
+ args[1].toInt()
+ } catch (e: NumberFormatException) {
+ pw.println("Error: task id should be an integer")
+ return false
+ }
return controller.moveToDesktop(taskId, WindowContainerTransaction())
}
@@ -75,12 +74,13 @@
return false
}
- val taskId = try {
- args[1].toInt()
- } catch (e: NumberFormatException) {
- pw.println("Error: task id should be an integer")
- return false
- }
+ val taskId =
+ try {
+ args[1].toInt()
+ } catch (e: NumberFormatException) {
+ pw.println("Error: task id should be an integer")
+ return false
+ }
controller.moveToNextDisplay(taskId)
return true
@@ -92,4 +92,4 @@
pw.println("$prefix moveToNextDisplay <taskId> ")
pw.println("$prefix Move a task with given id to next display.")
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 7e0234e..7d01580 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -32,9 +32,7 @@
import java.util.concurrent.Executor
import java.util.function.Consumer
-/**
- * Keeps track of task data related to desktop mode.
- */
+/** Keeps track of task data related to desktop mode. */
class DesktopModeTaskRepository {
/** Task data that is tracked per display */
@@ -48,12 +46,12 @@
val activeTasks: ArraySet<Int> = ArraySet(),
val visibleTasks: ArraySet<Int> = ArraySet(),
val minimizedTasks: ArraySet<Int> = ArraySet(),
+ // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
+ val freeformTasksInZOrder: ArrayList<Int> = ArrayList(),
)
// Token of the current wallpaper activity, used to remove it when the last task is removed
var wallpaperActivityToken: WindowContainerToken? = null
- // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
- private val freeformTasksInZOrder = mutableListOf<Int>()
private val activeTasksListeners = ArraySet<ActiveTasksListener>()
// Track visible tasks separately because a task may be part of the desktop but not visible.
private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>()
@@ -84,13 +82,8 @@
activeTasksListeners.add(activeTasksListener)
}
- /**
- * Add a [VisibleTasksListener] to be notified when freeform tasks are visible or not.
- */
- fun addVisibleTasksListener(
- visibleTasksListener: VisibleTasksListener,
- executor: Executor
- ) {
+ /** Add a [VisibleTasksListener] to be notified when freeform tasks are visible or not. */
+ fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) {
visibleTasksListeners[visibleTasksListener] = executor
displayData.keyIterator().forEach { displayId ->
val visibleTasksCount = getVisibleTaskCount(displayId)
@@ -112,9 +105,7 @@
}
}
- /**
- * Create a new merged region representative of all exclusion regions in all desktop tasks.
- */
+ /** Create a new merged region representative of all exclusion regions in all desktop tasks. */
private fun calculateDesktopExclusionRegion(): Region {
val desktopExclusionRegion = Region()
desktopExclusionRegions.valueIterator().forEach { taskExclusionRegion ->
@@ -123,16 +114,12 @@
return desktopExclusionRegion
}
- /**
- * Remove a previously registered [ActiveTasksListener]
- */
+ /** Remove a previously registered [ActiveTasksListener] */
fun removeActiveTasksListener(activeTasksListener: ActiveTasksListener) {
activeTasksListeners.remove(activeTasksListener)
}
- /**
- * Remove a previously registered [VisibleTasksListener]
- */
+ /** Remove a previously registered [VisibleTasksListener] */
fun removeVisibleTasksListener(visibleTasksListener: VisibleTasksListener) {
visibleTasksListeners.remove(visibleTasksListener)
}
@@ -182,18 +169,14 @@
return result
}
- /**
- * Check if a task with the given [taskId] was marked as an active task
- */
+ /** Check if a task with the given [taskId] was marked as an active task */
fun isActiveTask(taskId: Int): Boolean {
return displayData.valueIterator().asSequence().any { data ->
data.activeTasks.contains(taskId)
}
}
- /**
- * Whether a task is visible.
- */
+ /** Whether a task is visible. */
fun isVisibleTask(taskId: Int): Boolean {
return displayData.valueIterator().asSequence().any { data ->
data.visibleTasks.contains(taskId)
@@ -207,18 +190,14 @@
}
}
- /**
- * Check if a task with the given [taskId] is the only active task on its display
- */
+ /** Check if a task with the given [taskId] is the only active task on its display */
fun isOnlyActiveTask(taskId: Int): Boolean {
return displayData.valueIterator().asSequence().any { data ->
data.activeTasks.singleOrNull() == taskId
}
}
- /**
- * Get a set of the active tasks for given [displayId]
- */
+ /** Get a set of the active tasks for given [displayId] */
fun getActiveTasks(displayId: Int): ArraySet<Int> {
return ArraySet(displayData[displayId]?.activeTasks)
}
@@ -235,20 +214,16 @@
*/
fun getActiveNonMinimizedTasksOrderedFrontToBack(displayId: Int): List<Int> {
val activeTasks = getActiveTasks(displayId)
- val allTasksInZOrder = getFreeformTasksInZOrder()
+ val allTasksInZOrder = getFreeformTasksInZOrder(displayId)
return activeTasks
- // Don't show already minimized Tasks
- .filter { taskId -> !isMinimizedTask(taskId) }
- .sortedBy { taskId -> allTasksInZOrder.indexOf(taskId) }
+ // Don't show already minimized Tasks
+ .filter { taskId -> !isMinimizedTask(taskId) }
+ .sortedBy { taskId -> allTasksInZOrder.indexOf(taskId) }
}
- /**
- * Get a list of freeform tasks, ordered from top-bottom (top at index 0).
- */
- // TODO(b/278084491): pass in display id
- fun getFreeformTasksInZOrder(): List<Int> {
- return freeformTasksInZOrder
- }
+ /** Get a list of freeform tasks, ordered from top-bottom (top at index 0). */
+ fun getFreeformTasksInZOrder(displayId: Int): ArrayList<Int> =
+ ArrayList(displayData[displayId]?.freeformTasksInZOrder ?: emptyList())
/**
* Updates whether a freeform task with this id is visible or not and notifies listeners.
@@ -262,8 +237,10 @@
val otherDisplays = displayData.keyIterator().asSequence().filter { it != displayId }
for (otherDisplayId in otherDisplays) {
if (displayData[otherDisplayId].visibleTasks.remove(taskId)) {
- notifyVisibleTaskListeners(otherDisplayId,
- displayData[otherDisplayId].visibleTasks.size)
+ notifyVisibleTaskListeners(
+ otherDisplayId,
+ displayData[otherDisplayId].visibleTasks.size
+ )
}
}
} else if (displayId == INVALID_DISPLAY) {
@@ -310,9 +287,7 @@
}
}
- /**
- * Get number of tasks that are marked as visible on given [displayId]
- */
+ /** Get number of tasks that are marked as visible on given [displayId] */
fun getVisibleTaskCount(displayId: Int): Int {
KtProtoLog.d(
WM_SHELL_DESKTOP_MODE,
@@ -322,60 +297,62 @@
return displayData[displayId]?.visibleTasks?.size ?: 0
}
- /**
- * Add (or move if it already exists) the task to the top of the ordered list.
- */
- fun addOrMoveFreeformTaskToTop(taskId: Int) {
+ /** Add (or move if it already exists) the task to the top of the ordered list. */
+ // TODO(b/342417921): Identify if there is additional checks needed to move tasks for
+ // multi-display scenarios.
+ fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) {
KtProtoLog.d(
WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: add or move task to top taskId=%d",
+ "DesktopTaskRepo: add or move task to top: display=%d, taskId=%d",
+ displayId,
taskId
)
- if (freeformTasksInZOrder.contains(taskId)) {
- freeformTasksInZOrder.remove(taskId)
- }
- freeformTasksInZOrder.add(0, taskId)
+ displayData[displayId]?.freeformTasksInZOrder?.remove(taskId)
+ displayData.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId)
}
/** Mark a Task as minimized. */
fun minimizeTask(displayId: Int, taskId: Int) {
KtProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopModeTaskRepository: minimize Task: display=%d, task=%d",
- displayId, taskId)
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopModeTaskRepository: minimize Task: display=%d, task=%d",
+ displayId,
+ taskId
+ )
displayData.getOrCreate(displayId).minimizedTasks.add(taskId)
}
/** Mark a Task as non-minimized. */
fun unminimizeTask(displayId: Int, taskId: Int) {
KtProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopModeTaskRepository: unminimize Task: display=%d, task=%d",
- displayId, taskId)
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopModeTaskRepository: unminimize Task: display=%d, task=%d",
+ displayId,
+ taskId
+ )
displayData[displayId]?.minimizedTasks?.remove(taskId)
}
- /**
- * Remove the task from the ordered list.
- */
- fun removeFreeformTask(taskId: Int) {
+ /** Remove the task from the ordered list. */
+ fun removeFreeformTask(displayId: Int, taskId: Int) {
KtProtoLog.d(
WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: remove freeform task from ordered list taskId=%d",
+ "DesktopTaskRepo: remove freeform task from ordered list: display=%d, taskId=%d",
+ displayId,
taskId
)
- freeformTasksInZOrder.remove(taskId)
+ displayData[displayId]?.freeformTasksInZOrder?.remove(taskId)
boundsBeforeMaximizeByTaskId.remove(taskId)
KtProtoLog.d(
WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: remaining freeform tasks: %s", freeformTasksInZOrder.toDumpString(),
+ "DesktopTaskRepo: remaining freeform tasks: %s",
+ displayData[displayId]?.freeformTasksInZOrder?.toDumpString() ?: ""
)
}
/**
* Updates the active desktop gesture exclusion regions; if desktopExclusionRegions has been
- * accepted by desktopGestureExclusionListener, it will be updated in the
- * appropriate classes.
+ * accepted by desktopGestureExclusionListener, it will be updated in the appropriate classes.
*/
fun updateTaskExclusionRegions(taskId: Int, taskExclusionRegions: Region) {
desktopExclusionRegions.put(taskId, taskExclusionRegions)
@@ -385,9 +362,9 @@
}
/**
- * Removes the desktop gesture exclusion region for the specified task; if exclusionRegion
- * has been accepted by desktopGestureExclusionListener, it will be updated in the
- * appropriate classes.
+ * Removes the desktop gesture exclusion region for the specified task; if exclusionRegion has
+ * been accepted by desktopGestureExclusionListener, it will be updated in the appropriate
+ * classes.
*/
fun removeExclusionRegion(taskId: Int) {
desktopExclusionRegions.delete(taskId)
@@ -396,16 +373,12 @@
}
}
- /**
- * Removes and returns the bounds saved before maximizing the given task.
- */
+ /** Removes and returns the bounds saved before maximizing the given task. */
fun removeBoundsBeforeMaximize(taskId: Int): Rect? {
return boundsBeforeMaximizeByTaskId.removeReturnOld(taskId)
}
- /**
- * Saves the bounds of the given task before maximizing.
- */
+ /** Saves the bounds of the given task before maximizing. */
fun saveBoundsBeforeMaximize(taskId: Int, bounds: Rect) {
boundsBeforeMaximizeByTaskId.set(taskId, Rect(bounds))
}
@@ -414,7 +387,6 @@
val innerPrefix = "$prefix "
pw.println("${prefix}DesktopModeTaskRepository")
dumpDisplayData(pw, innerPrefix)
- pw.println("${innerPrefix}freeformTasksInZOrder=${freeformTasksInZOrder.toDumpString()}")
pw.println("${innerPrefix}activeTasksListeners=${activeTasksListeners.size}")
pw.println("${innerPrefix}visibleTasksListeners=${visibleTasksListeners.size}")
}
@@ -425,6 +397,9 @@
pw.println("${prefix}Display $displayId:")
pw.println("${innerPrefix}activeTasks=${data.activeTasks.toDumpString()}")
pw.println("${innerPrefix}visibleTasks=${data.visibleTasks.toDumpString()}")
+ pw.println(
+ "${innerPrefix}freeformTasksInZOrder=${data.freeformTasksInZOrder.toDumpString()}"
+ )
}
}
@@ -432,9 +407,7 @@
* Defines interface for classes that can listen to changes for active tasks in desktop mode.
*/
interface ActiveTasksListener {
- /**
- * Called when the active tasks change in desktop mode.
- */
+ /** Called when the active tasks change in desktop mode. */
fun onActiveTasksChanged(displayId: Int) {}
}
@@ -442,9 +415,7 @@
* Defines interface for classes that can listen to changes for visible tasks in desktop mode.
*/
interface VisibleTasksListener {
- /**
- * Called when the desktop changes the number of visible freeform tasks.
- */
+ /** Called when the desktop changes the number of visible freeform tasks. */
fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
index aa11a7d..a9d4e5f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
@@ -24,11 +24,11 @@
import com.android.wm.shell.dagger.WMSingleton
import javax.inject.Inject
-/**
- * Log Aster UIEvents for desktop windowing mode.
- */
+/** Log Aster UIEvents for desktop windowing mode. */
@WMSingleton
-class DesktopModeUiEventLogger @Inject constructor(
+class DesktopModeUiEventLogger
+@Inject
+constructor(
private val mUiEventLogger: UiEventLogger,
private val mInstanceIdSequence: InstanceIdSequence
) {
@@ -47,16 +47,14 @@
mUiEventLogger.log(event, uid, packageName)
}
- /**
- * Retrieves a new instance id for a new interaction.
- */
+ /** Retrieves a new instance id for a new interaction. */
fun getNewInstanceId(): InstanceId = mInstanceIdSequence.newInstanceId()
/**
* Logs an event as part of a particular CUI, on a particular package.
*
* @param instanceId The id identifying an interaction, potentially taking place across multiple
- * surfaces. There should be a new id generated for each distinct CUI.
+ * surfaces. There should be a new id generated for each distinct CUI.
* @param uid The user id associated with the package the user is interacting with
* @param packageName The name of the package the user is interacting with
* @param event The event type to generate
@@ -75,20 +73,15 @@
}
companion object {
- /**
- * Enums for logging desktop windowing mode UiEvents.
- */
+ /** Enums for logging desktop windowing mode UiEvents. */
enum class DesktopUiEventEnum(private val mId: Int) : UiEventLogger.UiEventEnum {
@UiEvent(doc = "Resize the window in desktop windowing mode by dragging the edge")
DESKTOP_WINDOW_EDGE_DRAG_RESIZE(1721),
-
@UiEvent(doc = "Resize the window in desktop windowing mode by dragging the corner")
DESKTOP_WINDOW_CORNER_DRAG_RESIZE(1722),
-
@UiEvent(doc = "Tap on the window header maximize button in desktop windowing mode")
DESKTOP_WINDOW_MAXIMIZE_BUTTON_TAP(1723),
-
@UiEvent(doc = "Double tap on window header to maximize it in desktop windowing mode")
DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_MAXIMIZE(1724);
@@ -97,4 +90,4 @@
private const val TAG = "DesktopModeUiEventLogger"
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index 6da3741..217b1d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -28,13 +28,11 @@
import android.util.Size
import com.android.wm.shell.common.DisplayLayout
+val DESKTOP_MODE_INITIAL_BOUNDS_SCALE: Float =
+ SystemProperties.getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f
-val DESKTOP_MODE_INITIAL_BOUNDS_SCALE: Float = SystemProperties
- .getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f
-
-val DESKTOP_MODE_LANDSCAPE_APP_PADDING: Int = SystemProperties
- .getInt("persist.wm.debug.desktop_mode_landscape_app_padding", 25)
-
+val DESKTOP_MODE_LANDSCAPE_APP_PADDING: Int =
+ SystemProperties.getInt("persist.wm.debug.desktop_mode_landscape_app_padding", 25)
/**
* Calculates the initial bounds required for an application to fill a scale of the display bounds
@@ -52,51 +50,53 @@
val idealSize = calculateIdealSize(screenBounds, scale)
// If no top activity exists, apps fullscreen bounds and aspect ratio cannot be calculated.
// Instead default to the desired initial bounds.
- val topActivityInfo = taskInfo.topActivityInfo
- ?: return positionInScreen(idealSize, screenBounds)
+ val topActivityInfo =
+ taskInfo.topActivityInfo ?: return positionInScreen(idealSize, screenBounds)
- val initialSize: Size = when (taskInfo.configuration.orientation) {
- ORIENTATION_LANDSCAPE -> {
- if (taskInfo.isResizeable) {
- if (isFixedOrientationPortrait(topActivityInfo.screenOrientation)) {
- // Respect apps fullscreen width
- Size(taskInfo.appCompatTaskInfo.topActivityLetterboxWidth, idealSize.height)
+ val initialSize: Size =
+ when (taskInfo.configuration.orientation) {
+ ORIENTATION_LANDSCAPE -> {
+ if (taskInfo.isResizeable) {
+ if (isFixedOrientationPortrait(topActivityInfo.screenOrientation)) {
+ // Respect apps fullscreen width
+ Size(taskInfo.appCompatTaskInfo.topActivityLetterboxWidth, idealSize.height)
+ } else {
+ idealSize
+ }
} else {
- idealSize
- }
- } else {
- maximumSizeMaintainingAspectRatio(taskInfo, idealSize,
- appAspectRatio)
- }
- }
- ORIENTATION_PORTRAIT -> {
- val customPortraitWidthForLandscapeApp = screenBounds.width() -
- (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2)
- if (taskInfo.isResizeable) {
- if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
- // Respect apps fullscreen height and apply custom app width
- Size(customPortraitWidthForLandscapeApp,
- taskInfo.appCompatTaskInfo.topActivityLetterboxHeight)
- } else {
- idealSize
- }
- } else {
- if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
- // Apply custom app width and calculate maximum size
- maximumSizeMaintainingAspectRatio(
- taskInfo,
- Size(customPortraitWidthForLandscapeApp, idealSize.height),
- appAspectRatio)
- } else {
- maximumSizeMaintainingAspectRatio(taskInfo, idealSize,
- appAspectRatio)
+ maximumSizeMaintainingAspectRatio(taskInfo, idealSize, appAspectRatio)
}
}
+ ORIENTATION_PORTRAIT -> {
+ val customPortraitWidthForLandscapeApp =
+ screenBounds.width() - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2)
+ if (taskInfo.isResizeable) {
+ if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
+ // Respect apps fullscreen height and apply custom app width
+ Size(
+ customPortraitWidthForLandscapeApp,
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight
+ )
+ } else {
+ idealSize
+ }
+ } else {
+ if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
+ // Apply custom app width and calculate maximum size
+ maximumSizeMaintainingAspectRatio(
+ taskInfo,
+ Size(customPortraitWidthForLandscapeApp, idealSize.height),
+ appAspectRatio
+ )
+ } else {
+ maximumSizeMaintainingAspectRatio(taskInfo, idealSize, appAspectRatio)
+ }
+ }
+ }
+ else -> {
+ idealSize
+ }
}
- else -> {
- idealSize
- }
- }
return positionInScreen(initialSize, screenBounds)
}
@@ -136,19 +136,17 @@
return Size(finalWidth, finalHeight)
}
-/**
- * Calculates the aspect ratio of an activity from its fullscreen bounds.
- */
+/** Calculates the aspect ratio of an activity from its fullscreen bounds. */
private fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) {
val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxWidth
val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxHeight
return maxOf(appLetterboxWidth, appLetterboxHeight) /
- minOf(appLetterboxWidth, appLetterboxHeight).toFloat()
+ minOf(appLetterboxWidth, appLetterboxHeight).toFloat()
}
val appBounds = taskInfo.configuration.windowConfiguration.appBounds ?: return 1f
return maxOf(appBounds.height(), appBounds.width()) /
- minOf(appBounds.height(), appBounds.width()).toFloat()
+ minOf(appBounds.height(), appBounds.width()).toFloat()
}
/**
@@ -161,13 +159,15 @@
return Size(width, height)
}
-/**
- * Adjusts bounds to be positioned in the middle of the screen.
- */
+/** Adjusts bounds to be positioned in the middle of the screen. */
private fun positionInScreen(desiredSize: Size, screenBounds: Rect): Rect {
// TODO(b/325240051): Position apps with bottom heavy offset
val heightOffset = (screenBounds.height() - desiredSize.height) / 2
val widthOffset = (screenBounds.width() - desiredSize.width) / 2
- return Rect(widthOffset, heightOffset,
- desiredSize.width + widthOffset, desiredSize.height + heightOffset)
+ return Rect(
+ widthOffset,
+ heightOffset,
+ desiredSize.width + widthOffset,
+ desiredSize.height + heightOffset
+ )
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 2e40ba7..38db1eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -17,10 +17,10 @@
package com.android.wm.shell.desktopmode;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -186,7 +186,7 @@
// In freeform, keep the top corners clear.
int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
? mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_split_from_desktop_height) :
+ com.android.wm.shell.R.dimen.desktop_mode_split_from_desktop_height) :
-captionHeight;
region.union(new Rect(0, transitionHeight, transitionEdgeWidth, layout.height()));
return region;
@@ -315,10 +315,11 @@
private static final float INDICATOR_FINAL_OPACITY = 0.35f;
private static final int MAXIMUM_OPACITY = 255;
- /** Determines how this animator will interact with the view's alpha:
- * Fade in, fade out, or no change to alpha
+ /**
+ * Determines how this animator will interact with the view's alpha:
+ * Fade in, fade out, or no change to alpha
*/
- private enum AlphaAnimType{
+ private enum AlphaAnimType {
ALPHA_FADE_IN_ANIM, ALPHA_FADE_OUT_ANIM, ALPHA_NO_CHANGE_ANIM
}
@@ -365,10 +366,10 @@
* Create animator for visual indicator changing type (i.e., fullscreen to freeform,
* freeform to split, etc.)
*
- * @param view the view for this indicator
+ * @param view the view for this indicator
* @param displayLayout information about the display the transitioning task is currently on
- * @param origType the original indicator type
- * @param newType the new indicator type
+ * @param origType the original indicator type
+ * @param newType the new indicator type
*/
private static VisualIndicatorAnimator animateIndicatorType(@NonNull View view,
@NonNull DisplayLayout displayLayout, IndicatorType origType,
@@ -469,7 +470,7 @@
*/
private static Rect getMaxBounds(Rect startBounds) {
return new Rect((int) (startBounds.left
- - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.width())),
+ - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.width())),
(int) (startBounds.top
- (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.height())),
(int) (startBounds.right
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index e5bf53a..6e45397 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -97,69 +97,74 @@
/** Handles moving tasks in and out of desktop */
class DesktopTasksController(
- private val context: Context,
- shellInit: ShellInit,
- private val shellCommandHandler: ShellCommandHandler,
- private val shellController: ShellController,
- private val displayController: DisplayController,
- private val shellTaskOrganizer: ShellTaskOrganizer,
- private val syncQueue: SyncTransactionQueue,
- private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
- private val dragAndDropController: DragAndDropController,
- private val transitions: Transitions,
- private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
- private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
- private val toggleResizeDesktopTaskTransitionHandler:
- ToggleResizeDesktopTaskTransitionHandler,
- private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler,
- private val desktopModeTaskRepository: DesktopModeTaskRepository,
- private val desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver,
- private val launchAdjacentController: LaunchAdjacentController,
- private val recentsTransitionHandler: RecentsTransitionHandler,
- private val multiInstanceHelper: MultiInstanceHelper,
- @ShellMainThread private val mainExecutor: ShellExecutor,
- private val desktopTasksLimiter: Optional<DesktopTasksLimiter>,
-) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler,
+ private val context: Context,
+ shellInit: ShellInit,
+ private val shellCommandHandler: ShellCommandHandler,
+ private val shellController: ShellController,
+ private val displayController: DisplayController,
+ private val shellTaskOrganizer: ShellTaskOrganizer,
+ private val syncQueue: SyncTransactionQueue,
+ private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ private val dragAndDropController: DragAndDropController,
+ private val transitions: Transitions,
+ private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
+ private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
+ private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
+ private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler,
+ private val desktopModeTaskRepository: DesktopModeTaskRepository,
+ private val desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver,
+ private val launchAdjacentController: LaunchAdjacentController,
+ private val recentsTransitionHandler: RecentsTransitionHandler,
+ private val multiInstanceHelper: MultiInstanceHelper,
+ @ShellMainThread private val mainExecutor: ShellExecutor,
+ private val desktopTasksLimiter: Optional<DesktopTasksLimiter>,
+) :
+ RemoteCallable<DesktopTasksController>,
+ Transitions.TransitionHandler,
DragAndDropController.DragAndDropListener {
private val desktopMode: DesktopModeImpl
private var visualIndicator: DesktopModeVisualIndicator? = null
private val desktopModeShellCommandHandler: DesktopModeShellCommandHandler =
DesktopModeShellCommandHandler(this)
- private val mOnAnimationFinishedCallback = Consumer<SurfaceControl.Transaction> {
- t: SurfaceControl.Transaction ->
- visualIndicator?.releaseVisualIndicator(t)
- visualIndicator = null
- }
- private val taskVisibilityListener = object : VisibleTasksListener {
- override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
- launchAdjacentController.launchAdjacentEnabled = visibleTasksCount == 0
- }
- }
- private val dragToDesktopStateListener = object : DragToDesktopStateListener {
- override fun onCommitToDesktopAnimationStart(tx: SurfaceControl.Transaction) {
- removeVisualIndicator(tx)
- }
-
- override fun onCancelToDesktopAnimationEnd(tx: SurfaceControl.Transaction) {
- removeVisualIndicator(tx)
- }
-
- private fun removeVisualIndicator(tx: SurfaceControl.Transaction) {
- visualIndicator?.releaseVisualIndicator(tx)
+ private val mOnAnimationFinishedCallback =
+ Consumer<SurfaceControl.Transaction> { t: SurfaceControl.Transaction ->
+ visualIndicator?.releaseVisualIndicator(t)
visualIndicator = null
}
- }
+ private val taskVisibilityListener =
+ object : VisibleTasksListener {
+ override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
+ launchAdjacentController.launchAdjacentEnabled = visibleTasksCount == 0
+ }
+ }
+ private val dragToDesktopStateListener =
+ object : DragToDesktopStateListener {
+ override fun onCommitToDesktopAnimationStart(tx: SurfaceControl.Transaction) {
+ removeVisualIndicator(tx)
+ }
+
+ override fun onCancelToDesktopAnimationEnd(tx: SurfaceControl.Transaction) {
+ removeVisualIndicator(tx)
+ }
+
+ private fun removeVisualIndicator(tx: SurfaceControl.Transaction) {
+ visualIndicator?.releaseVisualIndicator(tx)
+ visualIndicator = null
+ }
+ }
private val transitionAreaHeight
- get() = context.resources.getDimensionPixelSize(
+ get() =
+ context.resources.getDimensionPixelSize(
com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height
- )
+ )
private val transitionAreaWidth
- get() = context.resources.getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_width
- )
+ get() =
+ context.resources.getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_transition_area_width
+ )
/** Task id of the task currently being dragged from fullscreen/split. */
val draggingTaskId
@@ -178,11 +183,7 @@
private fun onInit() {
KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
shellCommandHandler.addDumpCallback(this::dump, this)
- shellCommandHandler.addCommandCallback(
- "desktopmode",
- desktopModeShellCommandHandler,
- this
- )
+ shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler, this)
shellController.addExternalInterface(
ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
{ createExternalInterface() },
@@ -232,9 +233,10 @@
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
// TODO(b/309014605): ensure remote transition is supplied once state is introduced
val transitionType = if (remoteTransition == null) TRANSIT_NONE else TRANSIT_TO_FRONT
- val handler = remoteTransition?.let {
- OneShotRemoteHandler(transitions.mainExecutor, remoteTransition)
- }
+ val handler =
+ remoteTransition?.let {
+ OneShotRemoteHandler(transitions.mainExecutor, remoteTransition)
+ }
transitions.startTransition(transitionType, wct, handler).also { t ->
handler?.setTransition(t)
}
@@ -253,9 +255,9 @@
val allFocusedTasks =
shellTaskOrganizer.getRunningTasks(displayId).filter { taskInfo ->
taskInfo.isFocused &&
- (taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN ||
- taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) &&
- taskInfo.activityType != ACTIVITY_TYPE_HOME
+ (taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN ||
+ taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) &&
+ taskInfo.activityType != ACTIVITY_TYPE_HOME
}
if (allFocusedTasks.isNotEmpty()) {
when (allFocusedTasks.size) {
@@ -278,7 +280,7 @@
KtProtoLog.w(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: Cannot enter desktop, expected less " +
- "than 3 focused tasks but found %d",
+ "than 3 focused tasks but found %d",
allFocusedTasks.size
)
}
@@ -288,27 +290,24 @@
/** Move a task with given `taskId` to desktop */
fun moveToDesktop(
- taskId: Int,
- wct: WindowContainerTransaction = WindowContainerTransaction()
+ taskId: Int,
+ wct: WindowContainerTransaction = WindowContainerTransaction()
): Boolean {
- shellTaskOrganizer.getRunningTaskInfo(taskId)?.let {
- task -> moveToDesktop(task, wct)
- } ?: return false
+ shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToDesktop(task, wct) }
+ ?: return false
return true
}
- /**
- * Move a task to desktop
- */
+ /** Move a task to desktop */
fun moveToDesktop(
- task: RunningTaskInfo,
- wct: WindowContainerTransaction = WindowContainerTransaction()
+ task: RunningTaskInfo,
+ wct: WindowContainerTransaction = WindowContainerTransaction()
) {
if (!DesktopModeStatus.canEnterDesktopMode(context)) {
KtProtoLog.w(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: Cannot enter desktop, " +
- "display does not meet minimum size requirements"
+ "display does not meet minimum size requirements"
)
return
}
@@ -316,7 +315,7 @@
KtProtoLog.w(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: Cannot enter desktop, " +
- "translucent top activity found. This is likely a modal dialog."
+ "translucent top activity found. This is likely a modal dialog."
)
return
}
@@ -328,7 +327,7 @@
exitSplitIfApplicable(wct, task)
// Bring other apps to front first
val taskToMinimize =
- bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
+ bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
addMoveToDesktopChanges(wct, task)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -340,21 +339,21 @@
}
/**
- * The first part of the animated drag to desktop transition. This is
- * followed with a call to [finalizeDragToDesktop] or [cancelDragToDesktop].
+ * The first part of the animated drag to desktop transition. This is followed with a call to
+ * [finalizeDragToDesktop] or [cancelDragToDesktop].
*/
fun startDragToDesktop(
- taskInfo: RunningTaskInfo,
- dragToDesktopValueAnimator: MoveToDesktopAnimator,
+ taskInfo: RunningTaskInfo,
+ dragToDesktopValueAnimator: MoveToDesktopAnimator,
) {
KtProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: startDragToDesktop taskId=%d",
- taskInfo.taskId
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: startDragToDesktop taskId=%d",
+ taskInfo.taskId
)
dragToDesktopTransitionHandler.startDragToDesktopTransition(
- taskInfo.taskId,
- dragToDesktopValueAnimator
+ taskInfo.taskId,
+ dragToDesktopValueAnimator
)
}
@@ -364,16 +363,15 @@
*/
private fun finalizeDragToDesktop(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
KtProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: finalizeDragToDesktop taskId=%d",
- taskInfo.taskId
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: finalizeDragToDesktop taskId=%d",
+ taskInfo.taskId
)
val wct = WindowContainerTransaction()
exitSplitIfApplicable(wct, taskInfo)
moveHomeTaskToFront(wct)
val taskToMinimize =
- bringDesktopAppsToFrontBeforeShowingNewTask(
- taskInfo.displayId, wct, taskInfo.taskId)
+ bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId)
addMoveToDesktopChanges(wct, taskInfo)
wct.setBounds(taskInfo.token, freeformBounds)
val transition = dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct)
@@ -381,9 +379,9 @@
}
/**
- * Perform needed cleanup transaction once animation is complete. Bounds need to be set
- * here instead of initial wct to both avoid flicker and to have task bounds to use for
- * the staging animation.
+ * Perform needed cleanup transaction once animation is complete. Bounds need to be set here
+ * instead of initial wct to both avoid flicker and to have task bounds to use for the staging
+ * animation.
*
* @param taskInfo task entering split that requires a bounds update
*/
@@ -395,16 +393,13 @@
}
/**
- * Perform clean up of the desktop wallpaper activity if the closed window task is
- * the last active task.
+ * Perform clean up of the desktop wallpaper activity if the closed window task is the last
+ * active task.
*
* @param wct transaction to modify if the last active task is closed
* @param taskId task id of the window that's being closed
*/
- fun onDesktopWindowClose(
- wct: WindowContainerTransaction,
- taskId: Int
- ) {
+ fun onDesktopWindowClose(wct: WindowContainerTransaction, taskId: Int) {
if (desktopModeTaskRepository.isOnlyActiveTask(taskId)) {
removeWallpaperActivity(wct)
}
@@ -419,8 +414,9 @@
/** Enter fullscreen by moving the focused freeform task in given `displayId` to fullscreen. */
fun enterFullscreen(displayId: Int) {
- getFocusedFreeformTask(displayId)
- ?.let { moveToFullscreenWithAnimation(it, it.positionInParent) }
+ getFocusedFreeformTask(displayId)?.let {
+ moveToFullscreenWithAnimation(it, it.positionInParent)
+ }
}
/** Move a desktop app to split screen. */
@@ -449,8 +445,7 @@
splitScreenController.getStageOfTask(taskInfo.taskId),
EXIT_REASON_DESKTOP_MODE
)
- splitScreenController.transitionHandler
- ?.onSplitToDesktop()
+ splitScreenController.transitionHandler?.onSplitToDesktop()
}
}
@@ -469,16 +464,16 @@
private fun moveToFullscreenWithAnimation(task: RunningTaskInfo, position: Point) {
KtProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: moveToFullscreen with animation taskId=%d",
- task.taskId
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: moveToFullscreen with animation taskId=%d",
+ task.taskId
)
val wct = WindowContainerTransaction()
addMoveToFullscreenChanges(wct, task)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
exitDesktopTaskTransitionHandler.startTransition(
- Transitions.TRANSIT_EXIT_DESKTOP_MODE,
+ Transitions.TRANSIT_EXIT_DESKTOP_MODE,
wct,
position,
mOnAnimationFinishedCallback
@@ -517,12 +512,12 @@
* Move task to the next display.
*
* Queries all current known display ids and sorts them in ascending order. Then iterates
- * through the list and looks for the display id that is larger than the display id for
- * the passed in task. If a display with a higher id is not found, iterates through the list and
+ * through the list and looks for the display id that is larger than the display id for the
+ * passed in task. If a display with a higher id is not found, iterates through the list and
* finds the first display id that is not the display id for the passed in task.
*
- * If a display matching the above criteria is found, re-parents the task to that display.
- * No-op if no such display is found.
+ * If a display matching the above criteria is found, re-parents the task to that display. No-op
+ * if no such display is found.
*/
fun moveToNextDisplay(taskId: Int) {
val task = shellTaskOrganizer.getRunningTaskInfo(taskId)
@@ -533,7 +528,7 @@
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"moveToNextDisplay: taskId=%d taskDisplayId=%d",
- taskId,
+ taskId,
task.displayId
)
@@ -560,7 +555,7 @@
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"moveToDisplay: taskId=%d displayId=%d",
- task.taskId,
+ task.taskId,
displayId
)
@@ -585,9 +580,9 @@
}
/**
- * Quick-resizes a desktop task, toggling between a fullscreen state (represented by the
- * stable bounds) and a free floating state (either the last saved bounds if available or the
- * default bounds otherwise).
+ * Quick-resizes a desktop task, toggling between a fullscreen state (represented by the stable
+ * bounds) and a free floating state (either the last saved bounds if available or the default
+ * bounds otherwise).
*/
fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo) {
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
@@ -600,11 +595,11 @@
// before the task was toggled to stable bounds were saved, toggle the task to those
// bounds. Otherwise, toggle to the default bounds.
val taskBoundsBeforeMaximize =
- desktopModeTaskRepository.removeBoundsBeforeMaximize(taskInfo.taskId)
+ desktopModeTaskRepository.removeBoundsBeforeMaximize(taskInfo.taskId)
if (taskBoundsBeforeMaximize != null) {
destinationBounds.set(taskBoundsBeforeMaximize)
} else {
- if (Flags.enableWindowingDynamicInitialBounds()){
+ if (Flags.enableWindowingDynamicInitialBounds()) {
destinationBounds.set(calculateInitialBounds(displayLayout, taskInfo))
} else {
destinationBounds.set(getDefaultDesktopTaskBounds(displayLayout))
@@ -650,8 +645,12 @@
val desiredHeight = (displayLayout.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
val heightOffset = (displayLayout.height() - desiredHeight) / 2
val widthOffset = (displayLayout.width() - desiredWidth) / 2
- return Rect(widthOffset, heightOffset,
- desiredWidth + widthOffset, desiredHeight + heightOffset)
+ return Rect(
+ widthOffset,
+ heightOffset,
+ desiredWidth + widthOffset,
+ desiredHeight + heightOffset
+ )
}
private fun getSnapBounds(taskInfo: RunningTaskInfo, position: SnapPosition): Rect {
@@ -693,19 +692,21 @@
}
private fun bringDesktopAppsToFrontBeforeShowingNewTask(
- displayId: Int,
- wct: WindowContainerTransaction,
- newTaskIdInFront: Int
+ displayId: Int,
+ wct: WindowContainerTransaction,
+ newTaskIdInFront: Int
): RunningTaskInfo? = bringDesktopAppsToFront(displayId, wct, newTaskIdInFront)
private fun bringDesktopAppsToFront(
- displayId: Int,
- wct: WindowContainerTransaction,
- newTaskIdInFront: Int? = null
+ displayId: Int,
+ wct: WindowContainerTransaction,
+ newTaskIdInFront: Int? = null
): RunningTaskInfo? {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: bringDesktopAppsToFront, newTaskIdInFront=%s",
- newTaskIdInFront ?: "null")
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: bringDesktopAppsToFront, newTaskIdInFront=%s",
+ newTaskIdInFront ?: "null"
+ )
if (Flags.enableDesktopWindowingWallpaperActivity()) {
// Add translucent wallpaper activity to show the wallpaper underneath
@@ -716,19 +717,25 @@
}
val nonMinimizedTasksOrderedFrontToBack =
- desktopModeTaskRepository.getActiveNonMinimizedTasksOrderedFrontToBack(displayId)
+ desktopModeTaskRepository.getActiveNonMinimizedTasksOrderedFrontToBack(displayId)
// If we're adding a new Task we might need to minimize an old one
val taskToMinimize: RunningTaskInfo? =
- if (newTaskIdInFront != null && desktopTasksLimiter.isPresent) {
- desktopTasksLimiter.get().getTaskToMinimizeIfNeeded(
- nonMinimizedTasksOrderedFrontToBack, newTaskIdInFront)
- } else { null }
+ if (newTaskIdInFront != null && desktopTasksLimiter.isPresent) {
+ desktopTasksLimiter
+ .get()
+ .getTaskToMinimizeIfNeeded(
+ nonMinimizedTasksOrderedFrontToBack,
+ newTaskIdInFront
+ )
+ } else {
+ null
+ }
nonMinimizedTasksOrderedFrontToBack
- // If there is a Task to minimize, let it stay behind the Home Task
- .filter { taskId -> taskId != taskToMinimize?.taskId }
- .mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) }
- .reversed() // Start from the back so the front task is brought forward last
- .forEach { task -> wct.reorder(task.token, true /* onTop */) }
+ // If there is a Task to minimize, let it stay behind the Home Task
+ .filter { taskId -> taskId != taskToMinimize?.taskId }
+ .mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) }
+ .reversed() // Start from the back so the front task is brought forward last
+ .forEach { task -> wct.reorder(task.token, true /* onTop */) }
return taskToMinimize
}
@@ -742,13 +749,19 @@
private fun addWallpaperActivity(wct: WindowContainerTransaction) {
KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: addWallpaper")
val intent = Intent(context, DesktopWallpaperActivity::class.java)
- val options = ActivityOptions.makeBasic().apply {
- isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
- pendingIntentBackgroundActivityStartMode =
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
- }
- val pendingIntent = PendingIntent.getActivity(context, /* requestCode = */ 0, intent,
- PendingIntent.FLAG_IMMUTABLE)
+ val options =
+ ActivityOptions.makeBasic().apply {
+ isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
+ pendingIntentBackgroundActivityStartMode =
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ }
+ val pendingIntent =
+ PendingIntent.getActivity(
+ context,
+ /* requestCode = */ 0,
+ intent,
+ PendingIntent.FLAG_IMMUTABLE
+ )
wct.sendPendingIntent(pendingIntent, intent, options.toBundle())
}
@@ -807,8 +820,7 @@
false
}
// Handle back navigation for the last window if wallpaper available
- shouldRemoveWallpaper(request) ->
- true
+ shouldRemoveWallpaper(request) -> true
// Only handle open or to front transitions
request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> {
reason = "transition type not handled (${request.type})"
@@ -826,7 +838,7 @@
}
// Only handle fullscreen or freeform tasks
triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN &&
- triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> {
+ triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> {
reason = "windowingMode not handled (${triggerTask.windowingMode})"
false
}
@@ -836,31 +848,32 @@
if (!shouldHandleRequest) {
KtProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: skipping handleRequest reason=%s",
- reason
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: skipping handleRequest reason=%s",
+ reason
)
return null
}
- val result = triggerTask?.let { task ->
- when {
- request.type == TRANSIT_TO_BACK -> handleBackNavigation(task)
- // Check if the task has a top transparent activity
- shouldLaunchAsModal(task) -> handleTransparentTaskLaunch(task)
- // Check if fullscreen task should be updated
- task.isFullscreen -> handleFullscreenTaskLaunch(task, transition)
- // Check if freeform task should be updated
- task.isFreeform -> handleFreeformTaskLaunch(task, transition)
- else -> {
- null
+ val result =
+ triggerTask?.let { task ->
+ when {
+ request.type == TRANSIT_TO_BACK -> handleBackNavigation(task)
+ // Check if the task has a top transparent activity
+ shouldLaunchAsModal(task) -> handleTransparentTaskLaunch(task)
+ // Check if fullscreen task should be updated
+ task.isFullscreen -> handleFullscreenTaskLaunch(task, transition)
+ // Check if freeform task should be updated
+ task.isFreeform -> handleFreeformTaskLaunch(task, transition)
+ else -> {
+ null
+ }
}
}
- }
KtProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: handleRequest result=%s",
- result ?: "null"
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: handleRequest result=%s",
+ result ?: "null"
)
return result
}
@@ -870,18 +883,15 @@
* This is intended to be used when desktop mode is part of another animation but isn't, itself,
* animating.
*/
- fun syncSurfaceState(
- info: TransitionInfo,
- finishTransaction: SurfaceControl.Transaction
- ) {
+ fun syncSurfaceState(info: TransitionInfo, finishTransaction: SurfaceControl.Transaction) {
// Add rounded corners to freeform windows
if (!DesktopModeStatus.useRoundedCorners()) {
return
}
val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
info.changes
- .filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM }
- .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
+ .filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM }
+ .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
}
private fun shouldLaunchAsModal(task: TaskInfo) =
@@ -889,23 +899,23 @@
private fun shouldRemoveWallpaper(request: TransitionRequestInfo): Boolean {
return Flags.enableDesktopWindowingWallpaperActivity() &&
- request.type == TRANSIT_TO_BACK &&
- request.triggerTask?.let { task ->
- desktopModeTaskRepository.isOnlyActiveTask(task.taskId)
- } ?: false
+ request.type == TRANSIT_TO_BACK &&
+ request.triggerTask?.let { task ->
+ desktopModeTaskRepository.isOnlyActiveTask(task.taskId)
+ } ?: false
}
private fun handleFreeformTaskLaunch(
- task: RunningTaskInfo,
- transition: IBinder
+ task: RunningTaskInfo,
+ transition: IBinder
): WindowContainerTransaction? {
KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch")
if (!desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
KtProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: switch freeform task to fullscreen oon transition" +
- " taskId=%d",
- task.taskId
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: switch freeform task to fullscreen oon transition" +
+ " taskId=%d",
+ task.taskId
)
return WindowContainerTransaction().also { wct ->
bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
@@ -927,16 +937,16 @@
}
private fun handleFullscreenTaskLaunch(
- task: RunningTaskInfo,
- transition: IBinder
+ task: RunningTaskInfo,
+ transition: IBinder
): WindowContainerTransaction? {
KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch")
if (desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
KtProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: switch fullscreen task to freeform on transition" +
- " taskId=%d",
- task.taskId
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: switch fullscreen task to freeform on transition" +
+ " taskId=%d",
+ task.taskId
)
return WindowContainerTransaction().also { wct ->
addMoveToDesktopChanges(wct, task)
@@ -952,21 +962,18 @@
// Always launch transparent tasks in fullscreen.
private fun handleTransparentTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
// Already fullscreen, no-op.
- if (task.isFullscreen)
- return null
- return WindowContainerTransaction().also { wct ->
- addMoveToFullscreenChanges(wct, task)
- }
+ if (task.isFullscreen) return null
+ return WindowContainerTransaction().also { wct -> addMoveToFullscreenChanges(wct, task) }
}
/** Handle back navigation by removing wallpaper activity if it's the last active task */
private fun handleBackNavigation(task: RunningTaskInfo): WindowContainerTransaction? {
- if (desktopModeTaskRepository.isOnlyActiveTask(task.taskId) &&
- desktopModeTaskRepository.wallpaperActivityToken != null) {
+ if (
+ desktopModeTaskRepository.isOnlyActiveTask(task.taskId) &&
+ desktopModeTaskRepository.wallpaperActivityToken != null
+ ) {
// Remove wallpaper activity when the last active task is removed
- return WindowContainerTransaction().also { wct ->
- removeWallpaperActivity(wct)
- }
+ return WindowContainerTransaction().also { wct -> removeWallpaperActivity(wct) }
} else {
return null
}
@@ -979,12 +986,13 @@
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!!
val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
- val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) {
- // Display windowing is freeform, set to undefined and inherit it
- WINDOWING_MODE_UNDEFINED
- } else {
- WINDOWING_MODE_FREEFORM
- }
+ val targetWindowingMode =
+ if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) {
+ // Display windowing is freeform, set to undefined and inherit it
+ WINDOWING_MODE_UNDEFINED
+ } else {
+ WINDOWING_MODE_FREEFORM
+ }
if (Flags.enableWindowingDynamicInitialBounds()) {
wct.setBounds(taskInfo.token, calculateInitialBounds(displayLayout, taskInfo))
}
@@ -1001,12 +1009,13 @@
) {
val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!!
val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
- val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FULLSCREEN) {
- // Display windowing is fullscreen, set to undefined and inherit it
- WINDOWING_MODE_UNDEFINED
- } else {
- WINDOWING_MODE_FULLSCREEN
- }
+ val targetWindowingMode =
+ if (tdaWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ // Display windowing is fullscreen, set to undefined and inherit it
+ WINDOWING_MODE_UNDEFINED
+ } else {
+ WINDOWING_MODE_FULLSCREEN
+ }
wct.setWindowingMode(taskInfo.token, targetWindowingMode)
wct.setBounds(taskInfo.token, Rect())
if (isDesktopDensityOverrideSet()) {
@@ -1018,10 +1027,7 @@
* Adds split screen changes to a transaction. Note that bounds are not reset here due to
* animation; see {@link onDesktopSplitSelectAnimComplete}
*/
- private fun addMoveToSplitChanges(
- wct: WindowContainerTransaction,
- taskInfo: RunningTaskInfo
- ) {
+ private fun addMoveToSplitChanges(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) {
// This windowing mode is to get the transition animation started; once we complete
// split select, we will change windowing mode to undefined and inherit from split stage.
// Going to undefined here causes task to flicker to the top left.
@@ -1034,38 +1040,35 @@
/** Returns the ID of the Task that will be minimized, or null if no task will be minimized. */
private fun addAndGetMinimizeChangesIfNeeded(
- displayId: Int,
- wct: WindowContainerTransaction,
- newTaskInfo: RunningTaskInfo
+ displayId: Int,
+ wct: WindowContainerTransaction,
+ newTaskInfo: RunningTaskInfo
): RunningTaskInfo? {
if (!desktopTasksLimiter.isPresent) return null
- return desktopTasksLimiter.get().addAndGetMinimizeTaskChangesIfNeeded(
- displayId, wct, newTaskInfo)
+ return desktopTasksLimiter
+ .get()
+ .addAndGetMinimizeTaskChangesIfNeeded(displayId, wct, newTaskInfo)
}
private fun addPendingMinimizeTransition(
- transition: IBinder,
- taskToMinimize: RunningTaskInfo?
+ transition: IBinder,
+ taskToMinimize: RunningTaskInfo?
) {
if (taskToMinimize == null) return
desktopTasksLimiter.ifPresent {
- it.addPendingMinimizeChange(
- transition, taskToMinimize.displayId, taskToMinimize.taskId)
+ it.addPendingMinimizeChange(transition, taskToMinimize.displayId, taskToMinimize.taskId)
}
}
/** Enter split by using the focused desktop task in given `displayId`. */
- fun enterSplit(
- displayId: Int,
- leftOrTop: Boolean
- ) {
+ fun enterSplit(displayId: Int, leftOrTop: Boolean) {
getFocusedFreeformTask(displayId)?.let { requestSplit(it, leftOrTop) }
}
private fun getFocusedFreeformTask(displayId: Int): RunningTaskInfo? {
- return shellTaskOrganizer.getRunningTasks(displayId)
- .find { taskInfo -> taskInfo.isFocused &&
- taskInfo.windowingMode == WINDOWING_MODE_FREEFORM }
+ return shellTaskOrganizer.getRunningTasks(displayId).find { taskInfo ->
+ taskInfo.isFocused && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
+ }
}
/**
@@ -1078,7 +1081,8 @@
leftOrTop: Boolean = false,
) {
val windowingMode = taskInfo.windowingMode
- if (windowingMode == WINDOWING_MODE_FULLSCREEN || windowingMode == WINDOWING_MODE_FREEFORM
+ if (
+ windowingMode == WINDOWING_MODE_FULLSCREEN || windowingMode == WINDOWING_MODE_FREEFORM
) {
val wct = WindowContainerTransaction()
addMoveToSplitChanges(wct, taskInfo)
@@ -1107,9 +1111,9 @@
/**
* Perform checks required on drag move. Create/release fullscreen indicator as needed.
- * Different sources for x and y coordinates are used due to different needs for each:
- * We want split transitions to be based on input coordinates but fullscreen transition
- * to be based on task edge coordinate.
+ * Different sources for x and y coordinates are used due to different needs for each: We want
+ * split transitions to be based on input coordinates but fullscreen transition to be based on
+ * task edge coordinate.
*
* @param taskInfo the task being dragged.
* @param taskSurface SurfaceControl of dragged task.
@@ -1133,9 +1137,16 @@
taskTop: Float
): DesktopModeVisualIndicator.IndicatorType {
// If the visual indicator does not exist, create it.
- val indicator = visualIndicator ?: DesktopModeVisualIndicator(
- syncQueue, taskInfo, displayController, context, taskSurface,
- rootTaskDisplayAreaOrganizer)
+ val indicator =
+ visualIndicator
+ ?: DesktopModeVisualIndicator(
+ syncQueue,
+ taskInfo,
+ displayController,
+ context,
+ taskSurface,
+ rootTaskDisplayAreaOrganizer
+ )
if (visualIndicator == null) visualIndicator = indicator
return indicator.updateIndicatorType(PointF(inputX, taskTop), taskInfo.windowingMode)
}
@@ -1161,10 +1172,11 @@
}
val indicator = visualIndicator ?: return
- val indicatorType = indicator.updateIndicatorType(
- PointF(inputCoordinate.x, taskBounds.top.toFloat()),
- taskInfo.windowingMode
- )
+ val indicatorType =
+ indicator.updateIndicatorType(
+ PointF(inputCoordinate.x, taskBounds.top.toFloat()),
+ taskInfo.windowingMode
+ )
when (indicatorType) {
DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
moveToFullscreenWithAnimation(taskInfo, position)
@@ -1180,8 +1192,12 @@
DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR -> {
// If task bounds are outside valid drag area, snap them inward and perform a
// transaction to set bounds.
- if (DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(
- taskBounds, validDragArea)) {
+ if (
+ DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(
+ taskBounds,
+ validDragArea
+ )
+ ) {
val wct = WindowContainerTransaction()
wct.setBounds(taskInfo.token, taskBounds)
transitions.startTransition(TRANSIT_CHANGE, wct, null)
@@ -1189,8 +1205,9 @@
releaseVisualIndicator()
}
DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
- throw IllegalArgumentException("Should not be receiving TO_DESKTOP_INDICATOR for " +
- "a freeform task.")
+ throw IllegalArgumentException(
+ "Should not be receiving TO_DESKTOP_INDICATOR for " + "a freeform task."
+ )
}
}
// A freeform drag-move ended, remove the indicator immediately.
@@ -1205,8 +1222,7 @@
*/
fun onDragPositioningEndThroughStatusBar(inputCoordinates: PointF, taskInfo: RunningTaskInfo) {
val indicator = getVisualIndicator() ?: return
- val indicatorType = indicator
- .updateIndicatorType(inputCoordinates, taskInfo.windowingMode)
+ val indicatorType = indicator.updateIndicatorType(inputCoordinates, taskInfo.windowingMode)
when (indicatorType) {
DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
@@ -1229,16 +1245,12 @@
}
}
- /**
- * Update the exclusion region for a specified task
- */
+ /** Update the exclusion region for a specified task */
fun onExclusionRegionChanged(taskId: Int, exclusionRegion: Region) {
desktopModeTaskRepository.updateTaskExclusionRegions(taskId, exclusionRegion)
}
- /**
- * Remove a previously tracked exclusion region for a specified task.
- */
+ /** Remove a previously tracked exclusion region for a specified task. */
fun removeExclusionRegionForTask(taskId: Int) {
desktopModeTaskRepository.removeExclusionRegion(taskId)
}
@@ -1259,10 +1271,7 @@
* @param listener the listener to add.
* @param callbackExecutor the executor to call the listener on.
*/
- fun setTaskRegionListener(
- listener: Consumer<Region>,
- callbackExecutor: Executor
- ) {
+ fun setTaskRegionListener(listener: Consumer<Region>, callbackExecutor: Executor) {
desktopModeTaskRepository.setExclusionRegionListener(listener, callbackExecutor)
}
@@ -1287,15 +1296,16 @@
}
// Start a new transition to launch the app
- val opts = ActivityOptions.makeBasic().apply {
- launchWindowingMode = WINDOWING_MODE_FREEFORM
- pendingIntentLaunchFlags =
- Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
- setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
- )
- isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
- }
+ val opts =
+ ActivityOptions.makeBasic().apply {
+ launchWindowingMode = WINDOWING_MODE_FREEFORM
+ pendingIntentLaunchFlags =
+ Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+ setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
+ )
+ isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
+ }
val wct = WindowContainerTransaction()
wct.sendPendingIntent(launchIntent, null, opts.toBundle())
transitions.startTransition(TRANSIT_OPEN, wct, null /* handler */)
@@ -1321,8 +1331,8 @@
@ExternalThread
private inner class DesktopModeImpl : DesktopMode {
override fun addVisibleTasksListener(
- listener: VisibleTasksListener,
- callbackExecutor: Executor
+ listener: VisibleTasksListener,
+ callbackExecutor: Executor
) {
mainExecutor.execute {
this@DesktopTasksController.addVisibleTasksListener(listener, callbackExecutor)
@@ -1330,8 +1340,8 @@
}
override fun addDesktopGestureExclusionRegionListener(
- listener: Consumer<Region>,
- callbackExecutor: Executor
+ listener: Consumer<Region>,
+ callbackExecutor: Executor
) {
mainExecutor.execute {
this@DesktopTasksController.setTaskRegionListener(listener, callbackExecutor)
@@ -1339,21 +1349,15 @@
}
override fun moveFocusedTaskToDesktop(displayId: Int) {
- mainExecutor.execute {
- this@DesktopTasksController.moveFocusedTaskToDesktop(displayId)
- }
+ mainExecutor.execute { this@DesktopTasksController.moveFocusedTaskToDesktop(displayId) }
}
override fun moveFocusedTaskToFullscreen(displayId: Int) {
- mainExecutor.execute {
- this@DesktopTasksController.enterFullscreen(displayId)
- }
+ mainExecutor.execute { this@DesktopTasksController.enterFullscreen(displayId) }
}
override fun moveFocusedTaskToStageSplit(displayId: Int, leftOrTop: Boolean) {
- mainExecutor.execute {
- this@DesktopTasksController.enterSplit(displayId, leftOrTop)
- }
+ mainExecutor.execute { this@DesktopTasksController.enterSplit(displayId, leftOrTop) }
}
}
@@ -1363,36 +1367,35 @@
IDesktopMode.Stub(), ExternalInterfaceBinder {
private lateinit var remoteListener:
- SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>
+ SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>
- private val listener: VisibleTasksListener = object : VisibleTasksListener {
- override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
- KtProtoLog.v(
+ private val listener: VisibleTasksListener =
+ object : VisibleTasksListener {
+ override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
+ KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"IDesktopModeImpl: onVisibilityChanged display=%d visible=%d",
displayId,
visibleTasksCount
- )
- remoteListener.call {
- l -> l.onTasksVisibilityChanged(displayId, visibleTasksCount)
+ )
+ remoteListener.call { l ->
+ l.onTasksVisibilityChanged(displayId, visibleTasksCount)
+ }
}
}
- }
init {
remoteListener =
- SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>(
- controller,
- { c ->
- c.desktopModeTaskRepository.addVisibleTasksListener(
- listener,
- c.mainExecutor
- )
- },
- { c ->
- c.desktopModeTaskRepository.removeVisibleTasksListener(listener)
- }
- )
+ SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>(
+ controller,
+ { c ->
+ c.desktopModeTaskRepository.addVisibleTasksListener(
+ listener,
+ c.mainExecutor
+ )
+ },
+ { c -> c.desktopModeTaskRepository.removeVisibleTasksListener(listener) }
+ )
}
/** Invalidates this instance, preventing future calls from updating the controller. */
@@ -1402,24 +1405,19 @@
}
override fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition?) {
- ExecutorUtils.executeRemoteCallWithTaskPermission(
- controller,
- "showDesktopApps"
- ) { c -> c.showDesktopApps(displayId, remoteTransition) }
+ ExecutorUtils.executeRemoteCallWithTaskPermission(controller, "showDesktopApps") { c ->
+ c.showDesktopApps(displayId, remoteTransition)
+ }
}
override fun showDesktopApp(taskId: Int) {
- ExecutorUtils.executeRemoteCallWithTaskPermission(
- controller,
- "showDesktopApp"
- ) { c -> c.moveTaskToFront(taskId) }
+ ExecutorUtils.executeRemoteCallWithTaskPermission(controller, "showDesktopApp") { c ->
+ c.moveTaskToFront(taskId)
+ }
}
override fun stashDesktopApps(displayId: Int) {
- KtProtoLog.w(
- WM_SHELL_DESKTOP_MODE,
- "IDesktopModeImpl: stashDesktopApps is deprecated"
- )
+ KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "IDesktopModeImpl: stashDesktopApps is deprecated")
}
override fun hideStashedDesktopApps(displayId: Int) {
@@ -1444,35 +1442,38 @@
ExecutorUtils.executeRemoteCallWithTaskPermission(
controller,
"onDesktopSplitSelectAnimComplete"
- ) { c -> c.onDesktopSplitSelectAnimComplete(taskInfo) }
+ ) { c ->
+ c.onDesktopSplitSelectAnimComplete(taskInfo)
+ }
}
override fun setTaskListener(listener: IDesktopTaskListener?) {
KtProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "IDesktopModeImpl: set task listener=%s",
- listener ?: "null"
+ WM_SHELL_DESKTOP_MODE,
+ "IDesktopModeImpl: set task listener=%s",
+ listener ?: "null"
)
- ExecutorUtils.executeRemoteCallWithTaskPermission(
- controller,
- "setTaskListener"
- ) { _ -> listener?.let { remoteListener.register(it) } ?: remoteListener.unregister() }
+ ExecutorUtils.executeRemoteCallWithTaskPermission(controller, "setTaskListener") { _ ->
+ listener?.let { remoteListener.register(it) } ?: remoteListener.unregister()
+ }
}
override fun moveToDesktop(taskId: Int) {
- ExecutorUtils.executeRemoteCallWithTaskPermission(
- controller,
- "moveToDesktop"
- ) { c -> c.moveToDesktop(taskId) }
+ ExecutorUtils.executeRemoteCallWithTaskPermission(controller, "moveToDesktop") { c ->
+ c.moveToDesktop(taskId)
+ }
}
}
companion object {
@JvmField
- val DESKTOP_MODE_INITIAL_BOUNDS_SCALE = SystemProperties
- .getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f
+ val DESKTOP_MODE_INITIAL_BOUNDS_SCALE =
+ SystemProperties.getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f
}
/** The positions on a screen that a task can snap to. */
- enum class SnapPosition { RIGHT, LEFT }
+ enum class SnapPosition {
+ RIGHT,
+ LEFT
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index e5e435d..98c79d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -50,26 +50,25 @@
* gesture.
*/
class DragToDesktopTransitionHandler(
- private val context: Context,
- private val transitions: Transitions,
- private val taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
- private val transactionSupplier: Supplier<SurfaceControl.Transaction>
+ private val context: Context,
+ private val transitions: Transitions,
+ private val taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ private val transactionSupplier: Supplier<SurfaceControl.Transaction>
) : TransitionHandler {
constructor(
- context: Context,
- transitions: Transitions,
- rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ context: Context,
+ transitions: Transitions,
+ rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
) : this(
- context,
- transitions,
- rootTaskDisplayAreaOrganizer,
- Supplier { SurfaceControl.Transaction() }
+ context,
+ transitions,
+ rootTaskDisplayAreaOrganizer,
+ Supplier { SurfaceControl.Transaction() }
)
private val rectEvaluator = RectEvaluator(Rect())
- private val launchHomeIntent = Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_HOME)
+ private val launchHomeIntent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
private var dragToDesktopStateListener: DragToDesktopStateListener? = null
private lateinit var splitScreenController: SplitScreenController
@@ -107,52 +106,55 @@
* after one of the "end" or "cancel" transitions is merged into this transition.
*/
fun startDragToDesktopTransition(
- taskId: Int,
- dragToDesktopAnimator: MoveToDesktopAnimator,
+ taskId: Int,
+ dragToDesktopAnimator: MoveToDesktopAnimator,
) {
if (inProgress) {
KtProtoLog.v(
- ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "DragToDesktop: Drag to desktop transition already in progress."
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DragToDesktop: Drag to desktop transition already in progress."
)
return
}
- val options = ActivityOptions.makeBasic().apply {
- setTransientLaunch()
- setSourceInfo(SourceInfo.TYPE_DESKTOP_ANIMATION, SystemClock.uptimeMillis())
- pendingIntentCreatorBackgroundActivityStartMode =
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
- }
- val pendingIntent = PendingIntent.getActivity(
+ val options =
+ ActivityOptions.makeBasic().apply {
+ setTransientLaunch()
+ setSourceInfo(SourceInfo.TYPE_DESKTOP_ANIMATION, SystemClock.uptimeMillis())
+ pendingIntentCreatorBackgroundActivityStartMode =
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ }
+ val pendingIntent =
+ PendingIntent.getActivity(
context,
0 /* requestCode */,
launchHomeIntent,
FLAG_MUTABLE or FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or FILL_IN_COMPONENT,
options.toBundle()
- )
+ )
val wct = WindowContainerTransaction()
wct.sendPendingIntent(pendingIntent, launchHomeIntent, Bundle())
- val startTransitionToken = transitions
- .startTransition(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, wct, this)
+ val startTransitionToken =
+ transitions.startTransition(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, wct, this)
- transitionState = if (isSplitTask(taskId)) {
- val otherTask = getOtherSplitTask(taskId) ?: throw IllegalStateException(
- "Expected split task to have a counterpart."
- )
- TransitionState.FromSplit(
+ transitionState =
+ if (isSplitTask(taskId)) {
+ val otherTask =
+ getOtherSplitTask(taskId)
+ ?: throw IllegalStateException("Expected split task to have a counterpart.")
+ TransitionState.FromSplit(
draggedTaskId = taskId,
dragAnimator = dragToDesktopAnimator,
startTransitionToken = startTransitionToken,
otherSplitTask = otherTask
- )
- } else {
- TransitionState.FromFullscreen(
+ )
+ } else {
+ TransitionState.FromFullscreen(
draggedTaskId = taskId,
dragAnimator = dragToDesktopAnimator,
startTransitionToken = startTransitionToken
- )
- }
+ )
+ }
}
/**
@@ -216,15 +218,16 @@
}
override fun startAnimation(
- transition: IBinder,
- info: TransitionInfo,
- startTransaction: SurfaceControl.Transaction,
- finishTransaction: SurfaceControl.Transaction,
- finishCallback: Transitions.TransitionFinishCallback
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: Transitions.TransitionFinishCallback
): Boolean {
val state = requireTransitionState()
- val isStartDragToDesktop = info.type == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP &&
+ val isStartDragToDesktop =
+ info.type == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP &&
transition == state.startTransitionToken
if (!isStartDragToDesktop) {
return false
@@ -257,13 +260,16 @@
when (state) {
is TransitionState.FromSplit -> {
state.splitRootChange = change
- val layer = if (!state.cancelled) {
- // Normal case, split root goes to the bottom behind everything else.
- appLayers - i
- } else {
- // Cancel-early case, pretend nothing happened so split root stays top.
- dragLayer
- }
+ val layer =
+ if (!state.cancelled) {
+ // Normal case, split root goes to the bottom behind everything
+ // else.
+ appLayers - i
+ } else {
+ // Cancel-early case, pretend nothing happened so split root stays
+ // top.
+ dragLayer
+ }
startTransaction.apply {
setLayer(change.leash, layer)
show(change.leash)
@@ -308,7 +314,10 @@
if (change.taskInfo?.taskId == state.draggedTaskId && !state.cancelled) {
state.draggedTaskChange = change
taskDisplayAreaOrganizer.reparentToDisplayArea(
- change.endDisplayId, change.leash, startTransaction)
+ change.endDisplayId,
+ change.leash,
+ startTransaction
+ )
val bounds = change.endAbsBounds
startTransaction.apply {
setLayer(change.leash, dragLayer)
@@ -339,28 +348,34 @@
}
override fun mergeAnimation(
- transition: IBinder,
- info: TransitionInfo,
- t: SurfaceControl.Transaction,
- mergeTarget: IBinder,
- finishCallback: Transitions.TransitionFinishCallback
+ transition: IBinder,
+ info: TransitionInfo,
+ t: SurfaceControl.Transaction,
+ mergeTarget: IBinder,
+ finishCallback: Transitions.TransitionFinishCallback
) {
val state = requireTransitionState()
- val isCancelTransition = info.type == TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP &&
+ val isCancelTransition =
+ info.type == TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP &&
transition == state.cancelTransitionToken &&
mergeTarget == state.startTransitionToken
- val isEndTransition = info.type == TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP &&
+ val isEndTransition =
+ info.type == TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP &&
mergeTarget == state.startTransitionToken
- val startTransactionFinishT = state.startTransitionFinishTransaction
+ val startTransactionFinishT =
+ state.startTransitionFinishTransaction
?: error("Start transition expected to be waiting for merge but wasn't")
- val startTransitionFinishCb = state.startTransitionFinishCb
+ val startTransitionFinishCb =
+ state.startTransitionFinishCb
?: error("Start transition expected to be waiting for merge but wasn't")
if (isEndTransition) {
info.changes.withIndex().forEach { (i, change) ->
// If we're exiting split, hide the remaining split task.
- if (state is TransitionState.FromSplit &&
- change.taskInfo?.taskId == state.otherSplitTask) {
+ if (
+ state is TransitionState.FromSplit &&
+ change.taskInfo?.taskId == state.otherSplitTask
+ ) {
t.hide(change.leash)
startTransactionFinishT.hide(change.leash)
}
@@ -373,14 +388,16 @@
state.draggedTaskChange = change
} else if (change.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM) {
// Other freeform tasks that are being restored go behind the dragged task.
- val draggedTaskLeash = state.draggedTaskChange?.leash
+ val draggedTaskLeash =
+ state.draggedTaskChange?.leash
?: error("Expected dragged leash to be non-null")
t.setRelativeLayer(change.leash, draggedTaskLeash, -i)
startTransactionFinishT.setRelativeLayer(change.leash, draggedTaskLeash, -i)
}
}
- val draggedTaskChange = state.draggedTaskChange
+ val draggedTaskChange =
+ state.draggedTaskChange
?: throw IllegalStateException("Expected non-null change of dragged task")
val draggedTaskLeash = draggedTaskChange.leash
val startBounds = draggedTaskChange.startAbsBounds
@@ -395,57 +412,59 @@
val startPosition = state.dragAnimator.position
val unscaledStartWidth = startBounds.width()
val unscaledStartHeight = startBounds.height()
- val unscaledStartBounds = Rect(
- startPosition.x.toInt(),
- startPosition.y.toInt(),
- startPosition.x.toInt() + unscaledStartWidth,
- startPosition.y.toInt() + unscaledStartHeight
- )
+ val unscaledStartBounds =
+ Rect(
+ startPosition.x.toInt(),
+ startPosition.y.toInt(),
+ startPosition.x.toInt() + unscaledStartWidth,
+ startPosition.y.toInt() + unscaledStartHeight
+ )
dragToDesktopStateListener?.onCommitToDesktopAnimationStart(t)
// Accept the merge by applying the merging transaction (applied by #showResizeVeil)
// and finish callback. Show the veil and position the task at the first frame before
// starting the final animation.
- onTaskResizeAnimationListener.onAnimationStart(state.draggedTaskId, t,
- unscaledStartBounds)
+ onTaskResizeAnimationListener.onAnimationStart(
+ state.draggedTaskId,
+ t,
+ unscaledStartBounds
+ )
finishCallback.onTransitionFinished(null /* wct */)
val tx: SurfaceControl.Transaction = transactionSupplier.get()
ValueAnimator.ofObject(rectEvaluator, unscaledStartBounds, endBounds)
- .setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
- .apply {
- addUpdateListener { animator ->
- val animBounds = animator.animatedValue as Rect
- val animFraction = animator.animatedFraction
- // Progress scale from starting value to 1 as animation plays.
- val animScale = startScale + animFraction * (1 - startScale)
- tx.apply {
- setScale(draggedTaskLeash, animScale, animScale)
- setPosition(
- draggedTaskLeash,
- animBounds.left.toFloat(),
- animBounds.top.toFloat()
- )
- setWindowCrop(
- draggedTaskLeash,
- animBounds.width(),
- animBounds.height()
- )
- }
- onTaskResizeAnimationListener.onBoundsChange(
- state.draggedTaskId,
- tx,
- animBounds
+ .setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
+ .apply {
+ addUpdateListener { animator ->
+ val animBounds = animator.animatedValue as Rect
+ val animFraction = animator.animatedFraction
+ // Progress scale from starting value to 1 as animation plays.
+ val animScale = startScale + animFraction * (1 - startScale)
+ tx.apply {
+ setScale(draggedTaskLeash, animScale, animScale)
+ setPosition(
+ draggedTaskLeash,
+ animBounds.left.toFloat(),
+ animBounds.top.toFloat()
)
+ setWindowCrop(draggedTaskLeash, animBounds.width(), animBounds.height())
}
- addListener(object : AnimatorListenerAdapter() {
+ onTaskResizeAnimationListener.onBoundsChange(
+ state.draggedTaskId,
+ tx,
+ animBounds
+ )
+ }
+ addListener(
+ object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
startTransitionFinishCb.onTransitionFinished(null /* null */)
clearState()
}
- })
- start()
- }
+ }
+ )
+ start()
+ }
} else if (isCancelTransition) {
info.changes.forEach { change ->
t.show(change.leash)
@@ -459,8 +478,8 @@
}
override fun handleRequest(
- transition: IBinder,
- request: TransitionRequestInfo
+ transition: IBinder,
+ request: TransitionRequestInfo
): WindowContainerTransaction? {
// Only handle transitions started from shell.
return null
@@ -489,8 +508,8 @@
val state = requireTransitionState()
val dragToDesktopAnimator = state.dragAnimator
- val draggedTaskChange = state.draggedTaskChange
- ?: throw IllegalStateException("Expected non-null task change")
+ val draggedTaskChange =
+ state.draggedTaskChange ?: throw IllegalStateException("Expected non-null task change")
val sc = draggedTaskChange.leash
// Pause the animation that shrinks the window when task is first dragged from fullscreen
dragToDesktopAnimator.cancelAnimator()
@@ -503,29 +522,31 @@
val dy = targetY - y
val tx: SurfaceControl.Transaction = transactionSupplier.get()
ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE, 1f)
- .setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
- .apply {
- addUpdateListener { animator ->
- val scale = animator.animatedValue as Float
- val fraction = animator.animatedFraction
- val animX = x + (dx * fraction)
- val animY = y + (dy * fraction)
- tx.apply {
- setPosition(sc, animX, animY)
- setScale(sc, scale, scale)
- show(sc)
- apply()
- }
+ .setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
+ .apply {
+ addUpdateListener { animator ->
+ val scale = animator.animatedValue as Float
+ val fraction = animator.animatedFraction
+ val animX = x + (dx * fraction)
+ val animY = y + (dy * fraction)
+ tx.apply {
+ setPosition(sc, animX, animY)
+ setScale(sc, scale, scale)
+ show(sc)
+ apply()
}
- addListener(object : AnimatorListenerAdapter() {
+ }
+ addListener(
+ object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
dragToDesktopStateListener?.onCancelToDesktopAnimationEnd(tx)
// Start the cancel transition to restore order.
startCancelDragToDesktopTransition()
}
- })
- start()
- }
+ }
+ )
+ start()
+ }
}
private fun startCancelDragToDesktopTransition() {
@@ -536,19 +557,23 @@
// There may have been tasks sent behind home that are not the dragged task (like
// when the dragged task is translucent and that makes the task behind it visible).
// Restore the order of those first.
- state.otherRootChanges.mapNotNull { it.container }.forEach { wc ->
- // TODO(b/322852244): investigate why even though these "other" tasks are
- // reordered in front of home and behind the translucent dragged task, its
- // surface is not visible on screen.
- wct.reorder(wc, true /* toTop */)
- }
- val wc = state.draggedTaskChange?.container
+ state.otherRootChanges
+ .mapNotNull { it.container }
+ .forEach { wc ->
+ // TODO(b/322852244): investigate why even though these "other" tasks are
+ // reordered in front of home and behind the translucent dragged task, its
+ // surface is not visible on screen.
+ wct.reorder(wc, true /* toTop */)
+ }
+ val wc =
+ state.draggedTaskChange?.container
?: error("Dragged task should be non-null before cancelling")
// Then the dragged task a the very top.
wct.reorder(wc, true /* toTop */)
}
is TransitionState.FromSplit -> {
- val wc = state.splitRootChange?.container
+ val wc =
+ state.splitRootChange?.container
?: error("Split root should be non-null before cancelling")
wct.reorder(wc, true /* toTop */)
}
@@ -556,8 +581,8 @@
val homeWc = state.homeToken ?: error("Home task should be non-null before cancelling")
wct.restoreTransientOrder(homeWc)
- state.cancelTransitionToken = transitions.startTransition(
- TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, wct, this)
+ state.cancelTransitionToken =
+ transitions.startTransition(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, wct, this)
}
private fun clearState() {
@@ -571,11 +596,12 @@
private fun getOtherSplitTask(taskId: Int): Int? {
val splitPos = splitScreenController.getSplitPosition(taskId)
if (splitPos == SPLIT_POSITION_UNDEFINED) return null
- val otherTaskPos = if (splitPos == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
- SPLIT_POSITION_TOP_OR_LEFT
- } else {
- SPLIT_POSITION_BOTTOM_OR_RIGHT
- }
+ val otherTaskPos =
+ if (splitPos == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
+ SPLIT_POSITION_TOP_OR_LEFT
+ } else {
+ SPLIT_POSITION_BOTTOM_OR_RIGHT
+ }
return splitScreenController.getTaskInfo(otherTaskPos)?.taskId
}
@@ -585,6 +611,7 @@
interface DragToDesktopStateListener {
fun onCommitToDesktopAnimationStart(tx: SurfaceControl.Transaction)
+
fun onCancelToDesktopAnimationEnd(tx: SurfaceControl.Transaction)
}
@@ -601,31 +628,32 @@
abstract var startAborted: Boolean
data class FromFullscreen(
- override val draggedTaskId: Int,
- override val dragAnimator: MoveToDesktopAnimator,
- override val startTransitionToken: IBinder,
- override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
- override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
- override var cancelTransitionToken: IBinder? = null,
- override var homeToken: WindowContainerToken? = null,
- override var draggedTaskChange: Change? = null,
- override var cancelled: Boolean = false,
- override var startAborted: Boolean = false,
- var otherRootChanges: MutableList<Change> = mutableListOf()
+ override val draggedTaskId: Int,
+ override val dragAnimator: MoveToDesktopAnimator,
+ override val startTransitionToken: IBinder,
+ override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
+ override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
+ override var cancelTransitionToken: IBinder? = null,
+ override var homeToken: WindowContainerToken? = null,
+ override var draggedTaskChange: Change? = null,
+ override var cancelled: Boolean = false,
+ override var startAborted: Boolean = false,
+ var otherRootChanges: MutableList<Change> = mutableListOf()
) : TransitionState()
+
data class FromSplit(
- override val draggedTaskId: Int,
- override val dragAnimator: MoveToDesktopAnimator,
- override val startTransitionToken: IBinder,
- override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
- override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
- override var cancelTransitionToken: IBinder? = null,
- override var homeToken: WindowContainerToken? = null,
- override var draggedTaskChange: Change? = null,
- override var cancelled: Boolean = false,
- override var startAborted: Boolean = false,
- var splitRootChange: Change? = null,
- var otherSplitTask: Int
+ override val draggedTaskId: Int,
+ override val dragAnimator: MoveToDesktopAnimator,
+ override val startTransitionToken: IBinder,
+ override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
+ override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
+ override var cancelTransitionToken: IBinder? = null,
+ override var homeToken: WindowContainerToken? = null,
+ override var draggedTaskChange: Change? = null,
+ override var cancelled: Boolean = false,
+ override var startAborted: Boolean = false,
+ var splitRootChange: Change? = null,
+ var otherSplitTask: Int
) : TransitionState()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 74b8f83..526cf4d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -59,6 +59,7 @@
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
private OnTaskResizeAnimationListener mOnTaskResizeAnimationListener;
+
public EnterDesktopTaskTransitionHandler(
Transitions transitions) {
this(transitions, SurfaceControl.Transaction::new);
@@ -72,11 +73,12 @@
}
void setOnTaskResizeAnimationListener(OnTaskResizeAnimationListener listener) {
- mOnTaskResizeAnimationListener = listener;
+ mOnTaskResizeAnimationListener = listener;
}
/**
* Starts Transition of type TRANSIT_MOVE_TO_DESKTOP
+ *
* @param wct WindowContainerTransaction for transition
* @return the token representing the started transition
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index 7342bd1..9f9e256 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -56,7 +56,7 @@
private final Transitions mTransitions;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
- private Supplier<SurfaceControl.Transaction> mTransactionSupplier;
+ private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private Point mPosition;
public ExitDesktopTaskTransitionHandler(
@@ -76,9 +76,10 @@
/**
* Starts Transition of a given type
- * @param type Transition type
- * @param wct WindowContainerTransaction for transition
- * @param position Position of the task when transition is started
+ *
+ * @param type Transition type
+ * @param wct WindowContainerTransaction for transition
+ * @param position Position of the task when transition is started
* @param onAnimationEndCallback to be called after animation
*/
public void startTransition(@WindowManager.TransitionType int type,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index c469e65..88d0554 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -86,9 +86,9 @@
.setWindowCrop(leash, startBounds.width(), startBounds.height())
.show(leash)
onTaskResizeAnimationListener.onAnimationStart(
- taskId,
- startTransaction,
- startBounds
+ taskId,
+ startTransaction,
+ startBounds
)
},
onEnd = {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index e0e2e706..7d2aa27 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -98,7 +98,7 @@
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
mDesktopModeTaskRepository.ifPresent(repository -> {
- repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
+ repository.addOrMoveFreeformTaskToTop(taskInfo.displayId, taskInfo.taskId);
repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId);
if (taskInfo.isVisible) {
if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) {
@@ -120,7 +120,7 @@
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
mDesktopModeTaskRepository.ifPresent(repository -> {
- repository.removeFreeformTask(taskInfo.taskId);
+ repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId);
repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId);
if (repository.removeActiveTask(taskInfo.taskId)) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
@@ -167,7 +167,7 @@
taskInfo.taskId, taskInfo.isFocused);
if (DesktopModeStatus.canEnterDesktopMode(mContext) && taskInfo.isFocused) {
mDesktopModeTaskRepository.ifPresent(repository -> {
- repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
+ repository.addOrMoveFreeformTaskToTop(taskInfo.displayId, taskInfo.taskId);
repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index c53e7fe..d8f2c02 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -253,8 +253,12 @@
notifyRunningTaskVanished(taskInfo);
}
- /** Notify listeners that the windowing mode of the given Task was updated. */
- public void onTaskWindowingModeChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ /**
+ * Notify listeners that the running infos related to recent tasks was updated.
+ *
+ * This currently includes windowing mode and visibility.
+ */
+ public void onTaskRunningInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
notifyRecentTasksChanged();
notifyRunningTaskChanged(taskInfo);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
index 6adbe4f..456658c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
@@ -16,7 +16,7 @@
package com.android.wm.shell.transition.tracing;
-import static android.tracing.perfetto.DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT;
+import static android.tracing.perfetto.DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP;
import android.internal.perfetto.protos.ShellTransitionOuterClass.ShellHandlerMapping;
import android.internal.perfetto.protos.ShellTransitionOuterClass.ShellHandlerMappings;
@@ -52,7 +52,7 @@
DataSourceParams params =
new DataSourceParams.Builder()
.setBufferExhaustedPolicy(
- PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)
+ PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP)
.build();
mDataSource.register(params);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 9afb057..74b091f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -278,11 +278,9 @@
public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
if (visible && stage != STAGE_TYPE_UNDEFINED) {
DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
- if (decor == null || !DesktopModeStatus.canEnterDesktopMode(mContext)
- || decor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
- return;
+ if (decor != null && DesktopModeStatus.canEnterDesktopMode(mContext)) {
+ mDesktopTasksController.moveToSplit(decor.mTaskInfo);
}
- mDesktopTasksController.moveToSplit(decor.mTaskInfo);
}
}
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
index 53bdda1..78f0ef7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
@@ -28,7 +28,6 @@
import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.ProgressBar
-import androidx.annotation.ColorInt
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
import androidx.core.content.ContextCompat
@@ -106,30 +105,17 @@
fun setAnimationTints(
darkMode: Boolean,
iconForegroundColor: ColorStateList? = null,
- baseForegroundColor: Int? = null
+ baseForegroundColor: Int? = null,
+ rippleDrawable: RippleDrawable? = null
) {
if (Flags.enableThemedAppHeaders()) {
requireNotNull(iconForegroundColor) { "Icon foreground color must be non-null" }
requireNotNull(baseForegroundColor) { "Base foreground color must be non-null" }
+ requireNotNull(rippleDrawable) { "Ripple drawable must be non-null" }
maximizeWindow.imageTintList = iconForegroundColor
- maximizeWindow.background = RippleDrawable(
- ColorStateList(
- arrayOf(
- intArrayOf(android.R.attr.state_hovered),
- intArrayOf(android.R.attr.state_pressed),
- intArrayOf(),
- ),
- intArrayOf(
- replaceColorAlpha(baseForegroundColor, OPACITY_8),
- replaceColorAlpha(baseForegroundColor, OPACITY_12),
- Color.TRANSPARENT
- )
- ),
- null,
- null
- )
+ maximizeWindow.background = rippleDrawable
progressBar.progressTintList = ColorStateList.valueOf(baseForegroundColor)
- .withAlpha(OPACITY_12)
+ .withAlpha(OPACITY_15)
progressBar.progressBackgroundTintList = ColorStateList.valueOf(Color.TRANSPARENT)
} else {
if (darkMode) {
@@ -146,18 +132,7 @@
}
}
- @ColorInt
- private fun replaceColorAlpha(@ColorInt color: Int, alpha: Int): Int {
- return Color.argb(
- alpha,
- Color.red(color),
- Color.green(color),
- Color.blue(color)
- )
- }
-
companion object {
- private const val OPACITY_8 = 20
- private const val OPACITY_12 = 31
+ private const val OPACITY_15 = 38
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
index 1c4b742..3c12da2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -9,6 +9,9 @@
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
+import android.graphics.drawable.RippleDrawable
+import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.RoundRectShape
import android.view.View
import android.view.View.OnLongClickListener
import android.widget.ImageButton
@@ -46,6 +49,38 @@
onMaximizeHoverAnimationFinishedListener: () -> Unit
) : DesktopModeWindowDecorationViewHolder(rootView) {
+ /**
+ * The corner radius to apply to the app chip, maximize and close button's background drawable.
+ **/
+ private val headerButtonsRippleRadius = context.resources
+ .getDimensionPixelSize(R.dimen.desktop_mode_header_buttons_ripple_radius)
+
+ /**
+ * The app chip, maximize and close button's height extends to the top & bottom edges of the
+ * header, and their width may be larger than their height. This is by design to increase the
+ * clickable and hover-able bounds of the view as much as possible. However, to prevent the
+ * ripple drawable from being as large as the views (and asymmetrical), insets are applied to
+ * the background ripple drawable itself to give the appearance of a smaller button
+ * (with padding between itself and the header edges / sibling buttons) but without affecting
+ * its touchable region.
+ */
+ private val appChipDrawableInsets = DrawableInsets(
+ vertical = context.resources
+ .getDimensionPixelSize(R.dimen.desktop_mode_header_app_chip_ripple_inset_vertical)
+ )
+ private val maximizeDrawableInsets = DrawableInsets(
+ vertical = context.resources
+ .getDimensionPixelSize(R.dimen.desktop_mode_header_maximize_ripple_inset_vertical),
+ horizontal = context.resources
+ .getDimensionPixelSize(R.dimen.desktop_mode_header_maximize_ripple_inset_horizontal)
+ )
+ private val closeDrawableInsets = DrawableInsets(
+ vertical = context.resources
+ .getDimensionPixelSize(R.dimen.desktop_mode_header_close_ripple_inset_vertical),
+ horizontal = context.resources
+ .getDimensionPixelSize(R.dimen.desktop_mode_header_close_ripple_inset_horizontal)
+ )
+
private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
private val captionHandle: View = rootView.requireViewById(R.id.caption_handle)
private val openMenuButton: View = rootView.requireViewById(R.id.open_menu_button)
@@ -97,7 +132,19 @@
maximizeWindowButton.imageAlpha = alpha
closeWindowButton.imageAlpha = alpha
expandMenuButton.imageAlpha = alpha
-
+ context.withStyledAttributes(
+ set = null,
+ attrs = intArrayOf(
+ android.R.attr.selectableItemBackground,
+ android.R.attr.selectableItemBackgroundBorderless
+ ),
+ defStyleAttr = 0,
+ defStyleRes = 0
+ ) {
+ openMenuButton.background = getDrawable(0)
+ maximizeWindowButton.background = getDrawable(1)
+ closeWindowButton.background = getDrawable(1)
+ }
maximizeButtonView.setAnimationTints(isDarkMode())
}
@@ -126,18 +173,40 @@
val foregroundColor = headerStyle.foreground.color
val foregroundAlpha = headerStyle.foreground.opacity
val colorStateList = ColorStateList.valueOf(foregroundColor).withAlpha(foregroundAlpha)
- closeWindowButton.imageTintList = colorStateList
- expandMenuButton.imageTintList = colorStateList
- with (appNameTextView) {
- isVisible = header.type == Header.Type.DEFAULT
- setTextColor(colorStateList)
+ // App chip.
+ openMenuButton.apply {
+ background = createRippleDrawable(
+ color = foregroundColor,
+ cornerRadius = headerButtonsRippleRadius,
+ drawableInsets = appChipDrawableInsets,
+ )
+ expandMenuButton.imageTintList = colorStateList
+ appNameTextView.apply {
+ isVisible = header.type == Header.Type.DEFAULT
+ setTextColor(colorStateList)
+ }
+ appIconImageView.imageAlpha = foregroundAlpha
}
- appIconImageView.imageAlpha = foregroundAlpha
+ // Maximize button.
maximizeButtonView.setAnimationTints(
darkMode = header.appTheme == Header.Theme.DARK,
iconForegroundColor = colorStateList,
- baseForegroundColor = foregroundColor
+ baseForegroundColor = foregroundColor,
+ rippleDrawable = createRippleDrawable(
+ color = foregroundColor,
+ cornerRadius = headerButtonsRippleRadius,
+ drawableInsets = maximizeDrawableInsets
+ )
)
+ // Close button.
+ closeWindowButton.apply {
+ imageTintList = colorStateList
+ background = createRippleDrawable(
+ color = foregroundColor,
+ cornerRadius = headerButtonsRippleRadius,
+ drawableInsets = closeDrawableInsets
+ )
+ }
}
override fun onHandleMenuOpened() {}
@@ -390,10 +459,61 @@
context.withStyledAttributes(null, intArrayOf(attr), 0, 0) {
return getColor(0, 0)
}
- return Color.BLACK
+ return Color.WHITE
}
- data class Header(
+ @ColorInt
+ private fun replaceColorAlpha(@ColorInt color: Int, alpha: Int): Int {
+ return Color.argb(
+ alpha,
+ Color.red(color),
+ Color.green(color),
+ Color.blue(color)
+ )
+ }
+
+ private fun createRippleDrawable(
+ @ColorInt color: Int,
+ cornerRadius: Int,
+ drawableInsets: DrawableInsets,
+ ): RippleDrawable {
+ return RippleDrawable(
+ ColorStateList(
+ arrayOf(
+ intArrayOf(android.R.attr.state_hovered),
+ intArrayOf(android.R.attr.state_pressed),
+ intArrayOf(),
+ ),
+ intArrayOf(
+ replaceColorAlpha(color, OPACITY_11),
+ replaceColorAlpha(color, OPACITY_15),
+ Color.TRANSPARENT
+ )
+ ),
+ null /* content */,
+ LayerDrawable(arrayOf(
+ ShapeDrawable().apply {
+ shape = RoundRectShape(
+ FloatArray(8) { cornerRadius.toFloat() },
+ null /* inset */,
+ null /* innerRadii */
+ )
+ paint.color = Color.WHITE
+ }
+ )).apply {
+ require(numberOfLayers == 1) { "Must only contain one layer" }
+ setLayerInset(0 /* index */,
+ drawableInsets.l, drawableInsets.t, drawableInsets.r, drawableInsets.b)
+ }
+ )
+ }
+
+ private data class DrawableInsets(val l: Int, val t: Int, val r: Int, val b: Int) {
+ constructor(vertical: Int = 0, horizontal: Int = 0) :
+ this(horizontal, vertical, horizontal, vertical)
+ }
+
+ private data class Header(
val type: Type,
val systemTheme: Theme,
val appTheme: Theme,
@@ -408,7 +528,7 @@
private fun Header.Theme.isDark(): Boolean = this == Header.Theme.DARK
- data class HeaderStyle(
+ private data class HeaderStyle(
val background: Background,
val foreground: Foreground
) {
@@ -497,6 +617,8 @@
private const val FOCUSED_OPACITY = 255
private const val OPACITY_100 = 255
+ private const val OPACITY_11 = 28
+ private const val OPACITY_15 = 38
private const val OPACITY_30 = 77
private const val OPACITY_55 = 140
private const val OPACITY_65 = 166
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
index f2f10ae..d03d779 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
@@ -188,6 +188,13 @@
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ /** {@inheritDoc} */
+ @Test
+ @FlakyTest(bugId = 336510055)
+ override fun entireScreenCovered() {
+ super.entireScreenCovered()
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 82c070c..f9b4108 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -20,6 +20,7 @@
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -62,6 +63,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
@@ -78,7 +80,7 @@
* Tests for the shell task organizer.
*
* Build/Install/Run:
- * atest WMShellUnitTests:ShellTaskOrganizerTests
+ * atest WMShellUnitTests:ShellTaskOrganizerTests
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -92,6 +94,8 @@
private ShellExecutor mTestExecutor;
@Mock
private ShellCommandHandler mShellCommandHandler;
+ @Mock
+ private RecentTasksController mRecentTasksController;
private ShellTaskOrganizer mOrganizer;
private ShellInit mShellInit;
@@ -120,6 +124,7 @@
private class TrackingLocusIdListener implements ShellTaskOrganizer.LocusIdListener {
final SparseArray<LocusId> visibleLocusTasks = new SparseArray<>();
final SparseArray<LocusId> invisibleLocusTasks = new SparseArray<>();
+
@Override
public void onVisibilityChanged(int taskId, LocusId locus, boolean visible) {
if (visible) {
@@ -130,18 +135,18 @@
}
}
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
try {
doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList())
.when(mTaskOrganizerController).registerTaskOrganizer(any());
- } catch (RemoteException e) {}
+ } catch (RemoteException e) {
+ }
mShellInit = spy(new ShellInit(mTestExecutor));
mOrganizer = spy(new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
- mTaskOrganizerController, mCompatUI, Optional.empty(), Optional.empty(),
- mTestExecutor));
+ mTaskOrganizerController, mCompatUI, Optional.empty(),
+ Optional.of(mRecentTasksController), mTestExecutor));
mShellInit.init();
}
@@ -163,7 +168,7 @@
@Test
public void testTaskLeashReleasedAfterVanished() throws RemoteException {
assumeFalse(ENABLE_SHELL_TRANSITIONS);
- RunningTaskInfo taskInfo = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
+ RunningTaskInfo taskInfo = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_MULTI_WINDOW);
SurfaceControl taskLeash = new SurfaceControl.Builder(new SurfaceSession())
.setName("task").build();
mOrganizer.registerOrganizer();
@@ -188,8 +193,8 @@
@Test
public void testRegisterWithExistingTasks() throws RemoteException {
// Setup some tasks
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW);
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_MULTI_WINDOW);
+ RunningTaskInfo task2 = createTaskInfo(/* taskId= */ 2, WINDOWING_MODE_MULTI_WINDOW);
ArrayList<TaskAppearedInfo> taskInfos = new ArrayList<>();
taskInfos.add(new TaskAppearedInfo(task1, new SurfaceControl()));
taskInfos.add(new TaskAppearedInfo(task2, new SurfaceControl()));
@@ -208,10 +213,10 @@
@Test
public void testAppearedVanished() {
- RunningTaskInfo taskInfo = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
+ RunningTaskInfo taskInfo = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_MULTI_WINDOW);
TrackingTaskListener listener = new TrackingTaskListener();
mOrganizer.addListenerForType(listener, TASK_LISTENER_TYPE_MULTI_WINDOW);
- mOrganizer.onTaskAppeared(taskInfo, null);
+ mOrganizer.onTaskAppeared(taskInfo, /* leash= */ null);
assertTrue(listener.appeared.contains(taskInfo));
mOrganizer.onTaskVanished(taskInfo);
@@ -220,7 +225,7 @@
@Test
public void testAddListenerExistingTasks() {
- RunningTaskInfo taskInfo = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
+ RunningTaskInfo taskInfo = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_MULTI_WINDOW);
mOrganizer.onTaskAppeared(taskInfo, null);
TrackingTaskListener listener = new TrackingTaskListener();
@@ -230,9 +235,9 @@
@Test
public void testAddListenerForMultipleTypes() {
- RunningTaskInfo taskInfo1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN);
+ RunningTaskInfo taskInfo1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
mOrganizer.onTaskAppeared(taskInfo1, null);
- RunningTaskInfo taskInfo2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW);
+ RunningTaskInfo taskInfo2 = createTaskInfo(/* taskId= */ 2, WINDOWING_MODE_MULTI_WINDOW);
mOrganizer.onTaskAppeared(taskInfo2, null);
TrackingTaskListener listener = new TrackingTaskListener();
@@ -247,10 +252,10 @@
@Test
public void testRemoveListenerForMultipleTypes() {
- RunningTaskInfo taskInfo1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN);
- mOrganizer.onTaskAppeared(taskInfo1, null);
- RunningTaskInfo taskInfo2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW);
- mOrganizer.onTaskAppeared(taskInfo2, null);
+ RunningTaskInfo taskInfo1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
+ mOrganizer.onTaskAppeared(taskInfo1, /* leash= */ null);
+ RunningTaskInfo taskInfo2 = createTaskInfo(/* taskId= */ 2, WINDOWING_MODE_MULTI_WINDOW);
+ mOrganizer.onTaskAppeared(taskInfo2, /* leash= */ null);
TrackingTaskListener listener = new TrackingTaskListener();
mOrganizer.addListenerForType(listener,
@@ -267,12 +272,12 @@
@Test
public void testWindowingModeChange() {
- RunningTaskInfo taskInfo = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
+ RunningTaskInfo taskInfo = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_MULTI_WINDOW);
TrackingTaskListener mwListener = new TrackingTaskListener();
TrackingTaskListener pipListener = new TrackingTaskListener();
mOrganizer.addListenerForType(mwListener, TASK_LISTENER_TYPE_MULTI_WINDOW);
mOrganizer.addListenerForType(pipListener, TASK_LISTENER_TYPE_PIP);
- mOrganizer.onTaskAppeared(taskInfo, null);
+ mOrganizer.onTaskAppeared(taskInfo, /* leash= */ null);
assertTrue(mwListener.appeared.contains(taskInfo));
assertTrue(pipListener.appeared.isEmpty());
@@ -284,11 +289,11 @@
@Test
public void testAddListenerForTaskId_afterTypeListener() {
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_MULTI_WINDOW);
TrackingTaskListener mwListener = new TrackingTaskListener();
TrackingTaskListener task1Listener = new TrackingTaskListener();
mOrganizer.addListenerForType(mwListener, TASK_LISTENER_TYPE_MULTI_WINDOW);
- mOrganizer.onTaskAppeared(task1, null);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
assertTrue(mwListener.appeared.contains(task1));
// Add task 1 specific listener
@@ -299,11 +304,11 @@
@Test
public void testAddListenerForTaskId_beforeTypeListener() {
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_MULTI_WINDOW);
TrackingTaskListener mwListener = new TrackingTaskListener();
TrackingTaskListener task1Listener = new TrackingTaskListener();
- mOrganizer.onTaskAppeared(task1, null);
- mOrganizer.addListenerForTaskId(task1Listener, 1);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+ mOrganizer.addListenerForTaskId(task1Listener, /* taskId= */ 1);
assertTrue(task1Listener.appeared.contains(task1));
mOrganizer.addListenerForType(mwListener, TASK_LISTENER_TYPE_MULTI_WINDOW);
@@ -312,7 +317,7 @@
@Test
public void testGetTaskListener() {
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_MULTI_WINDOW);
TrackingTaskListener mwListener = new TrackingTaskListener();
mOrganizer.addListenerForType(mwListener, TASK_LISTENER_TYPE_MULTI_WINDOW);
@@ -324,7 +329,7 @@
// Priority goes to the cookie listener so we would expect the task appear to show up there
// instead of the multi-window type listener.
- mOrganizer.onTaskAppeared(task1, null);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
assertTrue(cookieListener.appeared.contains(task1));
assertFalse(mwListener.appeared.contains(task1));
@@ -332,7 +337,7 @@
boolean gotException = false;
try {
- mOrganizer.addListenerForTaskId(task1Listener, 1);
+ mOrganizer.addListenerForTaskId(task1Listener, /* taskId= */ 1);
} catch (Exception e) {
gotException = true;
}
@@ -343,26 +348,27 @@
@Test
public void testGetParentTaskListener() {
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_MULTI_WINDOW);
TrackingTaskListener mwListener = new TrackingTaskListener();
- mOrganizer.onTaskAppeared(task1, null);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
mOrganizer.addListenerForTaskId(mwListener, task1.taskId);
RunningTaskInfo task2 = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
task2.parentTaskId = task1.taskId;
- mOrganizer.onTaskAppeared(task2, null);
+ mOrganizer.onTaskAppeared(task2, /* leash= */ null);
assertTrue(mwListener.appeared.contains(task2));
}
@Test
public void testOnSizeCompatActivityChanged() {
- final RunningTaskInfo taskInfo1 = createTaskInfo(12, WINDOWING_MODE_FULLSCREEN);
+ final RunningTaskInfo taskInfo1 = createTaskInfo(/* taskId= */ 12,
+ WINDOWING_MODE_FULLSCREEN);
taskInfo1.displayId = DEFAULT_DISPLAY;
taskInfo1.appCompatTaskInfo.topActivityInSizeCompat = false;
final TrackingTaskListener taskListener = new TrackingTaskListener();
mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
- mOrganizer.onTaskAppeared(taskInfo1, null);
+ mOrganizer.onTaskAppeared(taskInfo1, /* leash= */ null);
// sizeCompatActivity is null if top activity is not in size compat.
verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
@@ -394,12 +400,13 @@
@Test
public void testOnEligibleForLetterboxEducationActivityChanged() {
- final RunningTaskInfo taskInfo1 = createTaskInfo(12, WINDOWING_MODE_FULLSCREEN);
+ final RunningTaskInfo taskInfo1 = createTaskInfo(/* taskId= */ 12,
+ WINDOWING_MODE_FULLSCREEN);
taskInfo1.displayId = DEFAULT_DISPLAY;
taskInfo1.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = false;
final TrackingTaskListener taskListener = new TrackingTaskListener();
mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
- mOrganizer.onTaskAppeared(taskInfo1, null);
+ mOrganizer.onTaskAppeared(taskInfo1, /* leash= */ null);
// Task listener sent to compat UI is null if top activity isn't eligible for letterbox
// education.
@@ -433,13 +440,14 @@
@Test
public void testOnCameraCompatActivityChanged() {
- final RunningTaskInfo taskInfo1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN);
+ final RunningTaskInfo taskInfo1 = createTaskInfo(/* taskId= */ 1,
+ WINDOWING_MODE_FULLSCREEN);
taskInfo1.displayId = DEFAULT_DISPLAY;
taskInfo1.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState =
CAMERA_COMPAT_CONTROL_HIDDEN;
final TrackingTaskListener taskListener = new TrackingTaskListener();
mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
- mOrganizer.onTaskAppeared(taskInfo1, null);
+ mOrganizer.onTaskAppeared(taskInfo1, /* leash= */ null);
// Task listener sent to compat UI is null if top activity doesn't request a camera
// compat control.
@@ -510,20 +518,20 @@
@Test
public void testAddLocusListener() {
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_MULTI_WINDOW);
task1.isVisible = true;
task1.mTopActivityLocusId = new LocusId("10");
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_FULLSCREEN);
+ RunningTaskInfo task2 = createTaskInfo(/* taskId= */ 2, WINDOWING_MODE_FULLSCREEN);
task2.isVisible = true;
task2.mTopActivityLocusId = new LocusId("20");
- RunningTaskInfo task3 = createTaskInfo(3, WINDOWING_MODE_FULLSCREEN);
+ RunningTaskInfo task3 = createTaskInfo(/* taskId= */ 3, WINDOWING_MODE_FULLSCREEN);
task3.isVisible = true;
- mOrganizer.onTaskAppeared(task1, null);
- mOrganizer.onTaskAppeared(task2, null);
- mOrganizer.onTaskAppeared(task3, null);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+ mOrganizer.onTaskAppeared(task2, /* leash= */ null);
+ mOrganizer.onTaskAppeared(task3, /* leash= */ null);
TrackingLocusIdListener listener = new TrackingLocusIdListener();
mOrganizer.addLocusIdListener(listener);
@@ -539,11 +547,11 @@
TrackingLocusIdListener listener = new TrackingLocusIdListener();
mOrganizer.addLocusIdListener(listener);
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN);
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
task1.mTopActivityLocusId = new LocusId("10");
task1.isVisible = true;
- mOrganizer.onTaskAppeared(task1, null);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
assertTrue(listener.visibleLocusTasks.contains(task1.taskId));
assertEquals(listener.visibleLocusTasks.get(task1.taskId), task1.mTopActivityLocusId);
@@ -558,9 +566,9 @@
TrackingLocusIdListener listener = new TrackingLocusIdListener();
mOrganizer.addLocusIdListener(listener);
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_MULTI_WINDOW);
task1.isVisible = true;
- mOrganizer.onTaskAppeared(task1, null);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
assertEquals(listener.visibleLocusTasks.size(), 0);
task1.mTopActivityLocusId = new LocusId("10");
@@ -585,9 +593,9 @@
TrackingLocusIdListener listener = new TrackingLocusIdListener();
mOrganizer.addLocusIdListener(listener);
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN);
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
task1.isVisible = true;
- mOrganizer.onTaskAppeared(task1, null);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
task1.mTopActivityLocusId = new LocusId("10");
mOrganizer.onTaskInfoChanged(task1);
@@ -609,9 +617,9 @@
TrackingLocusIdListener listener = new TrackingLocusIdListener();
mOrganizer.addLocusIdListener(listener);
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_MULTI_WINDOW);
task1.isVisible = true;
- mOrganizer.onTaskAppeared(task1, null);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
assertEquals(listener.visibleLocusTasks.size(), 0);
assertEquals(listener.invisibleLocusTasks.size(), 0);
@@ -627,20 +635,63 @@
@Test
public void testOnSizeCompatRestartButtonClicked() throws RemoteException {
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_MULTI_WINDOW);
task1.token = mock(WindowContainerToken.class);
- mOrganizer.onTaskAppeared(task1, null);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
mOrganizer.onSizeCompatRestartButtonClicked(task1.taskId);
verify(mTaskOrganizerController).restartTaskTopActivityProcessIfVisible(task1.token);
}
+ @Test
+ public void testRecentTasks_onTaskAppeared_shouldNotifyTaskController() {
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FREEFORM);
+
+ mOrganizer.onTaskAppeared(task1, null);
+
+ verify(mRecentTasksController).onTaskAdded(task1);
+ }
+
+ @Test
+ public void testRecentTasks_onTaskVanished_shouldNotifyTaskController() {
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FREEFORM);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+
+ mOrganizer.onTaskVanished(task1);
+
+ verify(mRecentTasksController).onTaskRemoved(task1);
+ }
+
+ @Test
+ public void testRecentTasks_visibilityChanges_shouldNotifyTaskController() {
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FREEFORM);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+ RunningTaskInfo task2 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FREEFORM);
+ task2.isVisible = false;
+
+ mOrganizer.onTaskInfoChanged(task2);
+
+ verify(mRecentTasksController).onTaskRunningInfoChanged(task2);
+ }
+
+ @Test
+ public void testRecentTasks_windowingModeChanges_shouldNotifyTaskController() {
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+ RunningTaskInfo task2 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FREEFORM);
+
+ mOrganizer.onTaskInfoChanged(task2);
+
+ verify(mRecentTasksController).onTaskRunningInfoChanged(task2);
+ }
+
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
+ taskInfo.isVisible = true;
return taskInfo;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 8f59f30..310ccc2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -363,11 +363,11 @@
@Test
fun addOrMoveFreeformTaskToTop_didNotExist_addsToTop() {
- repo.addOrMoveFreeformTaskToTop(5)
- repo.addOrMoveFreeformTaskToTop(6)
- repo.addOrMoveFreeformTaskToTop(7)
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 5)
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 6)
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 7)
- val tasks = repo.getFreeformTasksInZOrder()
+ val tasks = repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)
assertThat(tasks.size).isEqualTo(3)
assertThat(tasks[0]).isEqualTo(7)
assertThat(tasks[1]).isEqualTo(6)
@@ -376,13 +376,13 @@
@Test
fun addOrMoveFreeformTaskToTop_alreadyExists_movesToTop() {
- repo.addOrMoveFreeformTaskToTop(5)
- repo.addOrMoveFreeformTaskToTop(6)
- repo.addOrMoveFreeformTaskToTop(7)
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 5)
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 6)
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 7)
- repo.addOrMoveFreeformTaskToTop(6)
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 6)
- val tasks = repo.getFreeformTasksInZOrder()
+ val tasks = repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)
assertThat(tasks.size).isEqualTo(3)
assertThat(tasks.first()).isEqualTo(6)
}
@@ -391,7 +391,7 @@
fun removeFreeformTask_removesTaskBoundsBeforeMaximize() {
val taskId = 1
repo.saveBoundsBeforeMaximize(taskId, Rect(0, 0, 200, 200))
- repo.removeFreeformTask(taskId)
+ repo.removeFreeformTask(THIRD_DISPLAY, taskId)
assertThat(repo.removeBoundsBeforeMaximize(taskId)).isNull()
}
@@ -480,31 +480,31 @@
@Test
fun getActiveNonMinimizedTasksOrderedFrontToBack_returnsFreeformTasksInCorrectOrder() {
- repo.addActiveTask(displayId = 0, taskId = 1)
- repo.addActiveTask(displayId = 0, taskId = 2)
- repo.addActiveTask(displayId = 0, taskId = 3)
+ repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1)
+ repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2)
+ repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 3)
// The front-most task will be the one added last through addOrMoveFreeformTaskToTop
- repo.addOrMoveFreeformTaskToTop(taskId = 3)
- repo.addOrMoveFreeformTaskToTop(taskId = 2)
- repo.addOrMoveFreeformTaskToTop(taskId = 1)
+ repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 3)
+ repo.addOrMoveFreeformTaskToTop(displayId = 0, taskId = 2)
+ repo.addOrMoveFreeformTaskToTop(displayId = 0, taskId = 1)
- assertThat(repo.getActiveNonMinimizedTasksOrderedFrontToBack(displayId = 0)).isEqualTo(
- listOf(1, 2, 3))
+ assertThat(repo.getActiveNonMinimizedTasksOrderedFrontToBack(displayId = 0))
+ .containsExactly(1, 2, 3).inOrder()
}
@Test
fun getActiveNonMinimizedTasksOrderedFrontToBack_minimizedTaskNotIncluded() {
- repo.addActiveTask(displayId = 0, taskId = 1)
- repo.addActiveTask(displayId = 0, taskId = 2)
- repo.addActiveTask(displayId = 0, taskId = 3)
+ repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1)
+ repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2)
+ repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 3)
// The front-most task will be the one added last through addOrMoveFreeformTaskToTop
- repo.addOrMoveFreeformTaskToTop(taskId = 3)
- repo.addOrMoveFreeformTaskToTop(taskId = 2)
- repo.addOrMoveFreeformTaskToTop(taskId = 1)
- repo.minimizeTask(displayId = 0, taskId = 2)
+ repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 3)
+ repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 2)
+ repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 1)
+ repo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
- assertThat(repo.getActiveNonMinimizedTasksOrderedFrontToBack(displayId = 0)).isEqualTo(
- listOf(1, 3))
+ assertThat(repo.getActiveNonMinimizedTasksOrderedFrontToBack(
+ displayId = DEFAULT_DISPLAY)).containsExactly(1, 3).inOrder()
}
@@ -544,5 +544,6 @@
companion object {
const val SECOND_DISPLAY = 1
+ const val THIRD_DISPLAY = 345
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index d8d534b..ac67bd1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -1614,7 +1614,7 @@
val task = createFreeformTask(displayId, bounds)
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
desktopModeTaskRepository.addActiveTask(displayId, task.taskId)
- desktopModeTaskRepository.addOrMoveFreeformTaskToTop(task.taskId)
+ desktopModeTaskRepository.addOrMoveFreeformTaskToTop(displayId, task.taskId)
runningTasks.add(task)
return task
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 3c488ca..77f917c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -298,7 +298,7 @@
val task = createFreeformTask(displayId)
`when`(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
desktopTaskRepo.addActiveTask(displayId, task.taskId)
- desktopTaskRepo.addOrMoveFreeformTaskToTop(task.taskId)
+ desktopTaskRepo.addOrMoveFreeformTaskToTop(displayId, task.taskId)
return task
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
index cd68c69..3f3cafc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -91,7 +91,8 @@
mFreeformTaskListener.onFocusTaskChanged(task);
- verify(mDesktopModeTaskRepository).addOrMoveFreeformTaskToTop(task.taskId);
+ verify(mDesktopModeTaskRepository)
+ .addOrMoveFreeformTaskToTop(task.displayId, task.taskId);
}
@Test
@@ -103,7 +104,7 @@
mFreeformTaskListener.onFocusTaskChanged(fullscreenTask);
verify(mDesktopModeTaskRepository, never())
- .addOrMoveFreeformTaskToTop(fullscreenTask.taskId);
+ .addOrMoveFreeformTaskToTop(fullscreenTask.displayId, fullscreenTask.taskId);
}
@After
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 884cb6e..56c4cea 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -511,7 +511,7 @@
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
- mRecentTasksControllerReal.onTaskWindowingModeChanged(taskInfo);
+ mRecentTasksControllerReal.onTaskRunningInfoChanged(taskInfo);
verify(mRecentTasksListener).onRunningTaskChanged(taskInfo);
}
@@ -525,7 +525,7 @@
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
- mRecentTasksControllerReal.onTaskWindowingModeChanged(taskInfo);
+ mRecentTasksControllerReal.onTaskRunningInfoChanged(taskInfo);
verify(mRecentTasksListener, never()).onRunningTaskChanged(any());
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 63b4538..386a606c 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -8617,6 +8617,7 @@
@SystemApi
@NonNull
@RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
+ // TODO also open to MODIFY_AUDIO_SETTINGS_PRIVILEGED b/341780042
public static List<AudioVolumeGroup> getAudioVolumeGroups() {
final IAudioService service = getService();
try {
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index e612645..c8b9da5 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -142,7 +142,7 @@
@UnsupportedAppUsage
int getStreamMaxVolume(int streamType);
- @EnforcePermission("MODIFY_AUDIO_ROUTING")
+ @EnforcePermission(anyOf={"MODIFY_AUDIO_SETTINGS_PRIVILEGED", "MODIFY_AUDIO_ROUTING"})
List<AudioVolumeGroup> getAudioVolumeGroups();
@EnforcePermission(anyOf={"MODIFY_AUDIO_SETTINGS_PRIVILEGED", "MODIFY_AUDIO_ROUTING"})
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 838630f..0589c0f12 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -523,7 +523,7 @@
private final int mConnectionState;
private final String mClientPackageName;
private final String mPackageName;
- private final int mVolumeHandling;
+ @PlaybackVolume private final int mVolumeHandling;
private final int mVolumeMax;
private final int mVolume;
private final String mAddress;
@@ -855,25 +855,7 @@
}
private void dumpVolume(@NonNull PrintWriter pw, @NonNull String prefix) {
- String volumeHandlingName;
-
- switch (mVolumeHandling) {
- case PLAYBACK_VOLUME_FIXED:
- volumeHandlingName = "FIXED";
- break;
- case PLAYBACK_VOLUME_VARIABLE:
- volumeHandlingName = "VARIABLE";
- break;
- default:
- volumeHandlingName = "UNKNOWN";
- break;
- }
-
- String volume = String.format(Locale.US,
- "volume(current=%d, max=%d, handling=%s(%d))",
- mVolume, mVolumeMax, volumeHandlingName, mVolumeHandling);
-
- pw.println(prefix + volume);
+ pw.println(prefix + getVolumeString(mVolume, mVolumeMax, mVolumeHandling));
}
@Override
@@ -936,47 +918,42 @@
@Override
public String toString() {
// Note: mExtras is not printed here.
- StringBuilder result =
- new StringBuilder()
- .append("MediaRoute2Info{ ")
- .append("id=")
- .append(getId())
- .append(", name=")
- .append(getName())
- .append(", type=")
- .append(getDeviceTypeString(getType()))
- .append(", isSystem=")
- .append(isSystemRoute())
- .append(", features=")
- .append(getFeatures())
- .append(", iconUri=")
- .append(getIconUri())
- .append(", description=")
- .append(getDescription())
- .append(", connectionState=")
- .append(getConnectionState())
- .append(", clientPackageName=")
- .append(getClientPackageName())
- .append(", volumeHandling=")
- .append(getVolumeHandling())
- .append(", volumeMax=")
- .append(getVolumeMax())
- .append(", volume=")
- .append(getVolume())
- .append(", address=")
- .append(getAddress())
- .append(", deduplicationIds=")
- .append(String.join(",", getDeduplicationIds()))
- .append(", providerId=")
- .append(getProviderId())
- .append(", isVisibilityRestricted=")
- .append(mIsVisibilityRestricted)
- .append(", allowedPackages=")
- .append(String.join(",", mAllowedPackages))
- .append(", suitabilityStatus=")
- .append(mSuitabilityStatus)
- .append(" }");
- return result.toString();
+ return new StringBuilder()
+ .append("MediaRoute2Info{ ")
+ .append("id=")
+ .append(getId())
+ .append(", name=")
+ .append(getName())
+ .append(", type=")
+ .append(getDeviceTypeString(getType()))
+ .append(", isSystem=")
+ .append(isSystemRoute())
+ .append(", features=")
+ .append(getFeatures())
+ .append(", iconUri=")
+ .append(getIconUri())
+ .append(", description=")
+ .append(getDescription())
+ .append(", connectionState=")
+ .append(getConnectionState())
+ .append(", clientPackageName=")
+ .append(getClientPackageName())
+ .append(", ")
+ .append(getVolumeString(mVolume, mVolumeMax, mVolumeHandling))
+ .append(", address=")
+ .append(getAddress())
+ .append(", deduplicationIds=")
+ .append(String.join(",", getDeduplicationIds()))
+ .append(", providerId=")
+ .append(getProviderId())
+ .append(", isVisibilityRestricted=")
+ .append(mIsVisibilityRestricted)
+ .append(", allowedPackages=")
+ .append(String.join(",", mAllowedPackages))
+ .append(", suitabilityStatus=")
+ .append(mSuitabilityStatus)
+ .append(" }")
+ .toString();
}
@Override
@@ -1008,6 +985,36 @@
dest.writeInt(mSuitabilityStatus);
}
+ /**
+ * Returns a human readable string describing the given volume values.
+ *
+ * @param volume The current volume.
+ * @param maxVolume The maximum volume.
+ * @param volumeHandling The {@link PlaybackVolume}.
+ */
+ /* package */ static String getVolumeString(
+ int volume, int maxVolume, @PlaybackVolume int volumeHandling) {
+ String volumeHandlingName;
+ switch (volumeHandling) {
+ case PLAYBACK_VOLUME_FIXED:
+ volumeHandlingName = "FIXED";
+ break;
+ case PLAYBACK_VOLUME_VARIABLE:
+ volumeHandlingName = "VARIABLE";
+ break;
+ default:
+ volumeHandlingName = "UNKNOWN";
+ break;
+ }
+ return String.format(
+ Locale.US,
+ "volume(current=%d, max=%d, handling=%s(%d))",
+ volume,
+ maxVolume,
+ volumeHandlingName,
+ volumeHandling);
+ }
+
private static String getDeviceTypeString(@Type int deviceType) {
switch (deviceType) {
case TYPE_BUILTIN_SPEAKER:
@@ -1079,7 +1086,7 @@
private int mConnectionState;
private String mClientPackageName;
private String mPackageName;
- private int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
+ @PlaybackVolume private int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
private int mVolumeMax;
private int mVolume;
private String mAddress;
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index e62d112..8fa0e49 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -402,9 +402,6 @@
@Nullable
public RoutingSessionInfo getRoutingSessionForMediaController(MediaController mediaController) {
MediaController.PlaybackInfo playbackInfo = mediaController.getPlaybackInfo();
- if (playbackInfo == null) {
- return null;
- }
if (playbackInfo.getPlaybackType() == MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
return getSystemRoutingSession(mediaController.getPackageName());
}
@@ -959,10 +956,6 @@
private boolean areSessionsMatched(MediaController mediaController,
RoutingSessionInfo sessionInfo) {
MediaController.PlaybackInfo playbackInfo = mediaController.getPlaybackInfo();
- if (playbackInfo == null) {
- return false;
- }
-
String volumeControlId = playbackInfo.getVolumeControlId();
if (volumeControlId == null) {
return false;
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index a3c8b68..9899e4e 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -107,7 +107,7 @@
@NonNull
final List<String> mTransferableRoutes;
- final int mVolumeHandling;
+ @MediaRoute2Info.PlaybackVolume final int mVolumeHandling;
final int mVolumeMax;
final int mVolume;
@@ -207,9 +207,10 @@
return controlHints;
}
+ @MediaRoute2Info.PlaybackVolume
private static int defineVolumeHandling(
boolean isSystemSession,
- int volumeHandling,
+ @MediaRoute2Info.PlaybackVolume int volumeHandling,
List<String> selectedRoutes,
boolean volumeAdjustmentForRemoteGroupSessions) {
if (!isSystemSession
@@ -439,9 +440,7 @@
pw.println(indent + "mSelectableRoutes=" + mSelectableRoutes);
pw.println(indent + "mDeselectableRoutes=" + mDeselectableRoutes);
pw.println(indent + "mTransferableRoutes=" + mTransferableRoutes);
- pw.println(indent + "mVolumeHandling=" + mVolumeHandling);
- pw.println(indent + "mVolumeMax=" + mVolumeMax);
- pw.println(indent + "mVolume=" + mVolume);
+ pw.println(indent + MediaRoute2Info.getVolumeString(mVolume, mVolumeMax, mVolumeHandling));
pw.println(indent + "mControlHints=" + mControlHints);
pw.println(indent + "mIsSystemSession=" + mIsSystemSession);
pw.println(indent + "mTransferReason=" + mTransferReason);
@@ -503,41 +502,36 @@
@Override
public String toString() {
- StringBuilder result =
- new StringBuilder()
- .append("RoutingSessionInfo{ ")
- .append("sessionId=")
- .append(getId())
- .append(", name=")
- .append(getName())
- .append(", clientPackageName=")
- .append(getClientPackageName())
- .append(", selectedRoutes={")
- .append(String.join(",", getSelectedRoutes()))
- .append("}")
- .append(", selectableRoutes={")
- .append(String.join(",", getSelectableRoutes()))
- .append("}")
- .append(", deselectableRoutes={")
- .append(String.join(",", getDeselectableRoutes()))
- .append("}")
- .append(", transferableRoutes={")
- .append(String.join(",", getTransferableRoutes()))
- .append("}")
- .append(", volumeHandling=")
- .append(getVolumeHandling())
- .append(", volumeMax=")
- .append(getVolumeMax())
- .append(", volume=")
- .append(getVolume())
- .append(", transferReason=")
- .append(getTransferReason())
- .append(", transferInitiatorUserHandle=")
- .append(getTransferInitiatorUserHandle())
- .append(", transferInitiatorPackageName=")
- .append(getTransferInitiatorPackageName())
- .append(" }");
- return result.toString();
+ return new StringBuilder()
+ .append("RoutingSessionInfo{ ")
+ .append("sessionId=")
+ .append(getId())
+ .append(", name=")
+ .append(getName())
+ .append(", clientPackageName=")
+ .append(getClientPackageName())
+ .append(", selectedRoutes={")
+ .append(String.join(",", getSelectedRoutes()))
+ .append("}")
+ .append(", selectableRoutes={")
+ .append(String.join(",", getSelectableRoutes()))
+ .append("}")
+ .append(", deselectableRoutes={")
+ .append(String.join(",", getDeselectableRoutes()))
+ .append("}")
+ .append(", transferableRoutes={")
+ .append(String.join(",", getTransferableRoutes()))
+ .append("}")
+ .append(", ")
+ .append(MediaRoute2Info.getVolumeString(mVolume, mVolumeMax, mVolumeHandling))
+ .append(", transferReason=")
+ .append(getTransferReason())
+ .append(", transferInitiatorUserHandle=")
+ .append(getTransferInitiatorUserHandle())
+ .append(", transferInitiatorPackageName=")
+ .append(getTransferInitiatorPackageName())
+ .append(" }")
+ .toString();
}
/**
@@ -586,6 +580,7 @@
private final List<String> mDeselectableRoutes;
@NonNull
private final List<String> mTransferableRoutes;
+ @MediaRoute2Info.PlaybackVolume
private int mVolumeHandling = MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
private int mVolumeMax;
private int mVolume;
@@ -906,6 +901,14 @@
* <p>By default the transfer initiation user handle and package name are set to {@code
* null}.
*/
+ // The UserHandleName warning suggests the name should be "doFooAsUser". But the UserHandle
+ // parameter of this function is stored in a field, and not used to execute an operation on
+ // a specific user.
+ // The MissingGetterMatchingBuilder requires a getTransferInitiator function. But said
+ // getter is not included because the returned package name and user handle is always either
+ // null or the values that correspond to the calling app, and that information is obtainable
+ // via RoutingController#wasTransferInitiatedBySelf.
+ @SuppressWarnings({"UserHandleName", "MissingGetterMatchingBuilder"})
@NonNull
@FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
public Builder setTransferInitiator(
diff --git a/media/java/android/media/audiopolicy/AudioVolumeGroup.java b/media/java/android/media/audiopolicy/AudioVolumeGroup.java
index d607126..0f5fbcc 100644
--- a/media/java/android/media/audiopolicy/AudioVolumeGroup.java
+++ b/media/java/android/media/audiopolicy/AudioVolumeGroup.java
@@ -31,6 +31,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
/**
* A class to create the association between different playback attributes
@@ -118,6 +119,12 @@
&& Arrays.equals(mAudioAttributes, thatAvg.mAudioAttributes);
}
+ @Override
+ public int hashCode() {
+ return Objects.hash(mName, mId,
+ Arrays.hashCode(mAudioAttributes), Arrays.hashCode(mLegacyStreamTypes));
+ }
+
/**
* @return List of {@link AudioAttributes} involved in this {@link AudioVolumeGroup}.
*/
diff --git a/media/java/android/media/projection/IMediaProjection.aidl b/media/java/android/media/projection/IMediaProjection.aidl
index 2fb0af5..7a1cf92 100644
--- a/media/java/android/media/projection/IMediaProjection.aidl
+++ b/media/java/android/media/projection/IMediaProjection.aidl
@@ -39,8 +39,8 @@
void unregisterCallback(IMediaProjectionCallback callback);
/**
- * Returns the {@link LaunchCookie} identifying the task to record, or {@code null} if
- * there is none.
+ * Returns the {@link LaunchCookie} identifying the task to record. Will always be set
+ * regardless of starting a new task or recent task
*/
@EnforcePermission("MANAGE_MEDIA_PROJECTION")
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
@@ -48,8 +48,16 @@
LaunchCookie getLaunchCookie();
/**
- * Updates the {@link LaunchCookie} identifying the task to record, or {@code null} if
- * there is none.
+ * Returns the taskId identifying the task to record. Will only be set in the case of
+ * launching a recent task, otherwise set to -1.
+ */
+ @EnforcePermission("MANAGE_MEDIA_PROJECTION")
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
+ int getTaskId();
+
+ /**
+ * Updates the {@link LaunchCookie} identifying the task to record.
*/
@EnforcePermission("MANAGE_MEDIA_PROJECTION")
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
@@ -57,6 +65,15 @@
void setLaunchCookie(in LaunchCookie launchCookie);
/**
+ * Updates the taskId identifying the task to record.
+ */
+ @EnforcePermission("MANAGE_MEDIA_PROJECTION")
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
+ void setTaskId(in int taskId);
+
+
+ /**
* Returns {@code true} if this token is still valid. A token is valid as long as the token
* hasn't timed out before it was used, and the token is only used once.
*
diff --git a/media/java/android/media/tv/TvStreamConfig.java b/media/java/android/media/tv/TvStreamConfig.java
index 7ea93b4..1f51c7a 100644
--- a/media/java/android/media/tv/TvStreamConfig.java
+++ b/media/java/android/media/tv/TvStreamConfig.java
@@ -23,6 +23,8 @@
import android.os.Parcelable;
import android.util.Log;
+import java.util.Objects;
+
/**
* @hide
*/
@@ -177,4 +179,9 @@
&& config.mMaxWidth == mMaxWidth
&& config.mMaxHeight == mMaxHeight;
}
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mGeneration, mStreamId, mType, mMaxWidth, mMaxHeight);
+ }
}
diff --git a/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java b/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java
index 0df36af..6860c0b 100644
--- a/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java
+++ b/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java
@@ -28,6 +28,7 @@
* outside the test it is implemented by the system server.
*/
public final class FakeIMediaProjection extends IMediaProjection.Stub {
+ int mTaskId = -1;
boolean mIsStarted = false;
LaunchCookie mLaunchCookie = null;
IMediaProjectionCallback mIMediaProjectionCallback = null;
@@ -87,6 +88,13 @@
@Override
@EnforcePermission(MANAGE_MEDIA_PROJECTION)
+ public int getTaskId() throws RemoteException {
+ getTaskId_enforcePermission();
+ return mTaskId;
+ }
+
+ @Override
+ @EnforcePermission(MANAGE_MEDIA_PROJECTION)
public void setLaunchCookie(LaunchCookie launchCookie) throws RemoteException {
setLaunchCookie_enforcePermission();
mLaunchCookie = launchCookie;
@@ -94,6 +102,13 @@
@Override
@EnforcePermission(MANAGE_MEDIA_PROJECTION)
+ public void setTaskId(int taskId) throws RemoteException {
+ setTaskId_enforcePermission();
+ mTaskId = taskId;
+ }
+
+ @Override
+ @EnforcePermission(MANAGE_MEDIA_PROJECTION)
public boolean isValid() throws RemoteException {
isValid_enforcePermission();
return true;
diff --git a/packages/InputDevices/res/raw/keyboard_layout_serbian_latin.kcm b/packages/InputDevices/res/raw/keyboard_layout_serbian_latin.kcm
new file mode 100644
index 0000000..0ff1dea
--- /dev/null
+++ b/packages/InputDevices/res/raw/keyboard_layout_serbian_latin.kcm
@@ -0,0 +1,350 @@
+# Copyright 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Serbian (Latin) keyboard layout.
+#
+
+type OVERLAY
+
+map key 12 SLASH
+map key 21 Z
+map key 44 Y
+map key 53 MINUS
+map key 86 PLUS
+
+### ROW 1
+
+key GRAVE {
+ label: '\u0327'
+ base: '\u0327'
+ shift: '~'
+}
+
+key 1 {
+ label: '1'
+ base: '1'
+ shift: '!'
+ ralt: '~'
+}
+
+key 2 {
+ label: '2'
+ base: '2'
+ shift: '\u0022'
+ ralt: '\u030C'
+}
+
+key 3 {
+ label: '3'
+ base: '3'
+ shift: '#'
+ ralt: '\u0302'
+}
+
+key 4 {
+ label: '4'
+ base: '4'
+ shift: '$'
+ ralt: '\u0306'
+}
+
+key 5 {
+ label: '5'
+ base: '5'
+ shift: '%'
+ ralt: '\u030A'
+}
+
+key 6 {
+ label: '6'
+ base: '6'
+ shift: '&'
+ ralt: '\u0328'
+}
+
+key 7 {
+ label: '7'
+ base: '7'
+ shift: '/'
+ ralt: '`'
+}
+
+key 8 {
+ label: '8'
+ base: '8'
+ shift: '('
+ ralt: '\u0307'
+}
+
+key 9 {
+ label: '9'
+ base: '9'
+ shift: ')'
+ ralt: '\u0301'
+}
+
+key 0 {
+ label: '0'
+ base: '0'
+ shift: '='
+ ralt: '\u030B'
+}
+
+key SLASH {
+ label: '\''
+ base: '\''
+ shift: '?'
+ ralt: '\u0308'
+}
+
+key EQUALS {
+ label: '+'
+ base: '+'
+ shift: '*'
+ ralt: '\u0327'
+}
+
+### ROW 2
+
+key Q {
+ label: 'Q'
+ base, capslock+shift: 'q'
+ shift, capslock: 'Q'
+ ralt: '\\'
+}
+
+key W {
+ label: 'W'
+ base, capslock+shift: 'w'
+ shift, capslock: 'W'
+ ralt: '|'
+}
+
+key E {
+ label: 'E'
+ base, capslock+shift: 'e'
+ shift, capslock: 'E'
+ ralt: '\u20ac'
+}
+
+key R {
+ label: 'R'
+ base, capslock+shift: 'r'
+ shift, capslock: 'R'
+}
+
+key T {
+ label: 'T'
+ base, capslock+shift: 't'
+ shift, capslock: 'T'
+}
+
+key Z {
+ label: 'Z'
+ base, capslock+shift: 'z'
+ shift, capslock: 'Z'
+}
+
+key U {
+ label: 'U'
+ base, capslock+shift: 'u'
+ shift, capslock: 'U'
+}
+
+key I {
+ label: 'I'
+ base, capslock+shift: 'i'
+ shift, capslock: 'I'
+}
+
+key O {
+ label: 'O'
+ base, capslock+shift: 'o'
+ shift, capslock: 'O'
+}
+
+key P {
+ label: 'P'
+ base, capslock+shift: 'p'
+ shift, capslock: 'P'
+}
+
+key LEFT_BRACKET {
+ label: '\u0160'
+ base, capslock+shift: '\u0161'
+ shift, capslock: '\u0160'
+ ralt: '\u00f7'
+}
+
+key RIGHT_BRACKET {
+ label: '\u0110'
+ base, capslock+shift: '\u0111'
+ shift, capslock: '\u0110'
+ ralt: '\u00d7'
+}
+
+### ROW 3
+
+key A {
+ label: 'A'
+ base, capslock+shift: 'a'
+ shift, capslock: 'A'
+}
+
+key S {
+ label: 'S'
+ base, capslock+shift: 's'
+ shift, capslock: 'S'
+}
+
+key D {
+ label: 'D'
+ base, capslock+shift: 'd'
+ shift, capslock: 'D'
+}
+
+key F {
+ label: 'F'
+ base, capslock+shift: 'f'
+ shift, capslock: 'F'
+ ralt: '['
+}
+
+key G {
+ label: 'G'
+ base, capslock+shift: 'g'
+ shift, capslock: 'G'
+ ralt: ']'
+}
+
+key H {
+ label: 'H'
+ base, capslock+shift: 'h'
+ shift, capslock: 'H'
+}
+
+key J {
+ label: 'J'
+ base, capslock+shift: 'j'
+ shift, capslock: 'J'
+}
+
+key K {
+ label: 'K'
+ base, capslock+shift: 'k'
+ shift, capslock: 'K'
+ ralt: '\u0142'
+}
+
+key L {
+ label: 'L'
+ base, capslock+shift: 'l'
+ shift, capslock: 'L'
+ ralt: '\u0141'
+}
+
+key SEMICOLON {
+ label: '\u010c'
+ base, capslock+shift: '\u010d'
+ shift, capslock: '\u010c'
+}
+
+key APOSTROPHE {
+ label: '\u0106'
+ base, capslock+shift: '\u0107'
+ shift, capslock: '\u0106'
+ ralt: '\u00df'
+}
+
+key BACKSLASH {
+ label: '\u017d'
+ base, capslock+shift: '\u017e'
+ shift, capslock: '\u017d'
+ ralt: '\u00a4'
+}
+
+### ROW 4
+
+key PLUS {
+ label: '<'
+ base: '<'
+ shift: '>'
+}
+
+key Y {
+ label: 'Y'
+ base, capslock+shift: 'y'
+ shift, capslock: 'Y'
+}
+
+key X {
+ label: 'X'
+ base, capslock+shift: 'x'
+ shift, capslock: 'X'
+}
+
+key C {
+ label: 'C'
+ base, capslock+shift: 'c'
+ shift, capslock: 'C'
+}
+
+key V {
+ label: 'V'
+ base, capslock+shift: 'v'
+ shift, capslock: 'V'
+ ralt: '@'
+}
+
+key B {
+ label: 'B'
+ base, capslock+shift: 'b'
+ shift, capslock: 'B'
+ ralt: '{'
+}
+
+key N {
+ label: 'N'
+ base, capslock+shift: 'n'
+ shift, capslock: 'N'
+ ralt: '}'
+}
+
+key M {
+ label: 'M'
+ base, capslock+shift: 'm'
+ shift, capslock: 'M'
+ ralt: '\u00a7'
+}
+
+key COMMA {
+ label: ','
+ base: ','
+ shift: ';'
+ ralt: '<'
+}
+
+key PERIOD {
+ label: '.'
+ base: '.'
+ shift: ':'
+ ralt: '>'
+}
+
+key MINUS {
+ label: '-'
+ base: '-'
+ shift: '_'
+}
\ No newline at end of file
diff --git a/packages/InputDevices/res/values/strings.xml b/packages/InputDevices/res/values/strings.xml
index e10bd7f..be7d2c1 100644
--- a/packages/InputDevices/res/values/strings.xml
+++ b/packages/InputDevices/res/values/strings.xml
@@ -152,4 +152,10 @@
<!-- Thai (Pattachote variant) keyboard layout label. [CHAR LIMIT=35] -->
<string name="keyboard_layout_thai_pattachote">Thai (Pattachote)</string>
+
+ <!-- Serbian (Latin) keyboard layout label. [CHAR LIMIT=35] -->
+ <string name="keyboard_layout_serbian_latin">Serbian (Latin)</string>
+
+ <!-- Montenegrin (Latin) keyboard layout label. [CHAR LIMIT=35] -->
+ <string name="keyboard_layout_montenegrin_latin">Montenegrin (Latin)</string>
</resources>
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index d4f8f7d..84e4b9e 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -332,4 +332,18 @@
android:keyboardLayout="@raw/keyboard_layout_thai_pattachote"
android:keyboardLocale="th-Thai"
android:keyboardLayoutType="extended" />
+
+ <keyboard-layout
+ android:name="keyboard_layout_serbian_latin"
+ android:label="@string/keyboard_layout_serbian_latin"
+ android:keyboardLayout="@raw/keyboard_layout_serbian_latin"
+ android:keyboardLocale="sr-Latn-RS"
+ android:keyboardLayoutType="qwertz" />
+
+ <keyboard-layout
+ android:name="keyboard_layout_montenegrin_latin"
+ android:label="@string/keyboard_layout_montenegrin_latin"
+ android:keyboardLayout="@raw/keyboard_layout_serbian_latin"
+ android:keyboardLocale="cnr-Latn-ME"
+ android:keyboardLayoutType="qwertz" />
</keyboard-layouts>
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
index 1040ac6..c5e86b4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
@@ -210,7 +210,7 @@
mAppContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled(
pkg, uid, ActivityManager.RESTRICTION_LEVEL_EXEMPTED,
true, ActivityManager.RESTRICTION_REASON_USER,
- "settings", 0);
+ "settings", ActivityManager.RESTRICTION_SOURCE_USER, 0);
}
}
@@ -251,7 +251,7 @@
mAppContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled(
pkg, uid, ActivityManager.RESTRICTION_LEVEL_EXEMPTED,
false, ActivityManager.RESTRICTION_REASON_USER,
- "settings", 0);
+ "settings", ActivityManager.RESTRICTION_SOURCE_USER, 0L);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/view/accessibility/data/repository/CaptioningRepository.kt b/packages/SettingsLib/src/com/android/settingslib/view/accessibility/data/repository/CaptioningRepository.kt
index 5bcb82d..0b71d25 100644
--- a/packages/SettingsLib/src/com/android/settingslib/view/accessibility/data/repository/CaptioningRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/view/accessibility/data/repository/CaptioningRepository.kt
@@ -27,6 +27,7 @@
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -62,16 +63,18 @@
captioningChanges
.filterIsInstance(CaptioningChange.IsSystemAudioCaptioningEnabled::class)
.map { it.isEnabled }
+ .onStart { emit(captioningManager.isSystemAudioCaptioningEnabled) }
.stateIn(
coroutineScope,
SharingStarted.WhileSubscribed(),
- captioningManager.isSystemAudioCaptioningEnabled
+ captioningManager.isSystemAudioCaptioningEnabled,
)
override val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean> =
captioningChanges
.filterIsInstance(CaptioningChange.IsSystemUICaptioningEnabled::class)
.map { it.isEnabled }
+ .onStart { emit(captioningManager.isSystemAudioCaptioningUiEnabled) }
.stateIn(
coroutineScope,
SharingStarted.WhileSubscribed(),
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 3b84333..8204569 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -16,9 +16,13 @@
package com.android.settingslib.volume.data.repository
+import android.content.ContentResolver
+import android.database.ContentObserver
import android.media.AudioDeviceInfo
import android.media.AudioManager
import android.media.AudioManager.OnCommunicationDeviceChangedListener
+import android.provider.Settings
+import androidx.concurrent.futures.DirectExecutor
import com.android.internal.util.ConcurrentUtils
import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
import com.android.settingslib.volume.shared.model.AudioManagerEvent
@@ -33,12 +37,13 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -84,17 +89,31 @@
class AudioRepositoryImpl(
private val audioManagerEventsReceiver: AudioManagerEventsReceiver,
private val audioManager: AudioManager,
+ private val contentResolver: ContentResolver,
private val backgroundCoroutineContext: CoroutineContext,
private val coroutineScope: CoroutineScope,
) : AudioRepository {
+ private val streamSettingNames: Map<AudioStream, String> =
+ mapOf(
+ AudioStream(AudioManager.STREAM_VOICE_CALL) to Settings.System.VOLUME_VOICE,
+ AudioStream(AudioManager.STREAM_SYSTEM) to Settings.System.VOLUME_SYSTEM,
+ AudioStream(AudioManager.STREAM_RING) to Settings.System.VOLUME_RING,
+ AudioStream(AudioManager.STREAM_MUSIC) to Settings.System.VOLUME_MUSIC,
+ AudioStream(AudioManager.STREAM_ALARM) to Settings.System.VOLUME_ALARM,
+ AudioStream(AudioManager.STREAM_NOTIFICATION) to Settings.System.VOLUME_NOTIFICATION,
+ AudioStream(AudioManager.STREAM_BLUETOOTH_SCO) to Settings.System.VOLUME_BLUETOOTH_SCO,
+ AudioStream(AudioManager.STREAM_ACCESSIBILITY) to Settings.System.VOLUME_ACCESSIBILITY,
+ AudioStream(AudioManager.STREAM_ASSISTANT) to Settings.System.VOLUME_ASSISTANT,
+ )
+
override val mode: StateFlow<Int> =
callbackFlow {
- val listener =
- AudioManager.OnModeChangedListener { newMode -> launch { send(newMode) } }
+ val listener = AudioManager.OnModeChangedListener { newMode -> trySend(newMode) }
audioManager.addOnModeChangedListener(ConcurrentUtils.DIRECT_EXECUTOR, listener)
awaitClose { audioManager.removeOnModeChangedListener(listener) }
}
+ .onStart { emit(audioManager.mode) }
.flowOn(backgroundCoroutineContext)
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode)
@@ -102,6 +121,7 @@
audioManagerEventsReceiver.events
.filterIsInstance(AudioManagerEvent.InternalRingerModeChanged::class)
.map { RingerMode(audioManager.ringerModeInternal) }
+ .onStart { emit(RingerMode(audioManager.ringerModeInternal)) }
.flowOn(backgroundCoroutineContext)
.stateIn(
coroutineScope,
@@ -122,6 +142,7 @@
}
.filterNotNull()
.map { audioManager.communicationDevice }
+ .onStart { emit(audioManager.communicationDevice) }
.flowOn(backgroundCoroutineContext)
.stateIn(
coroutineScope,
@@ -130,17 +151,18 @@
)
override fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> {
- return audioManagerEventsReceiver.events
- .filter {
- if (it is StreamAudioManagerEvent) {
- it.audioStream == audioStream
- } else {
- true
- }
- }
+ return merge(
+ audioManagerEventsReceiver.events.filter {
+ if (it is StreamAudioManagerEvent) {
+ it.audioStream == audioStream
+ } else {
+ true
+ }
+ },
+ volumeSettingChanges(audioStream),
+ )
.map { getCurrentAudioStream(audioStream) }
.onStart { emit(getCurrentAudioStream(audioStream)) }
- .conflate()
.flowOn(backgroundCoroutineContext)
}
@@ -195,4 +217,19 @@
// return STREAM_VOICE_CALL in getAudioStream
audioManager.getStreamMinVolume(AudioManager.STREAM_VOICE_CALL)
}
+
+ private fun volumeSettingChanges(audioStream: AudioStream): Flow<Unit> {
+ val uri = streamSettingNames[audioStream]?.let(Settings.System::getUriFor)
+ uri ?: return emptyFlow()
+ return callbackFlow {
+ val observer =
+ object : ContentObserver(DirectExecutor.INSTANCE, 0) {
+ override fun onChange(selfChange: Boolean) {
+ launch { send(Unit) }
+ }
+ }
+ contentResolver.registerContentObserver(uri, false, observer)
+ awaitClose { contentResolver.unregisterContentObserver(observer) }
+ }
+ }
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
index 8700680..683759d 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -16,6 +16,8 @@
package com.android.settingslib.volume.data.repository
+import android.content.ContentResolver
+import android.database.ContentObserver
import android.media.AudioDeviceInfo
import android.media.AudioManager
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -38,6 +40,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mock
@@ -55,9 +58,11 @@
@Captor
private lateinit var communicationDeviceListenerCaptor:
ArgumentCaptor<AudioManager.OnCommunicationDeviceChangedListener>
+ @Captor private lateinit var contentObserver: ArgumentCaptor<ContentObserver>
@Mock private lateinit var audioManager: AudioManager
@Mock private lateinit var communicationDevice: AudioDeviceInfo
+ @Mock private lateinit var contentResolver: ContentResolver
private val eventsReceiver = FakeAudioManagerEventsReceiver()
private val volumeByStream: MutableMap<Int, Int> = mutableMapOf()
@@ -80,6 +85,7 @@
val streamType = it.arguments[0] as Int
volumeByStream[it.arguments[0] as Int] = it.arguments[1] as Int
triggerEvent(AudioManagerEvent.StreamVolumeChanged(AudioStream(streamType)))
+ triggerSettingChange()
}
`when`(audioManager.adjustStreamVolume(anyInt(), anyInt(), anyInt())).then {
val streamType = it.arguments[0] as Int
@@ -100,6 +106,7 @@
AudioRepositoryImpl(
eventsReceiver,
audioManager,
+ contentResolver,
testScope.testScheduler,
testScope.backgroundScope,
)
@@ -254,6 +261,12 @@
modeListenerCaptor.value.onModeChanged(mode)
}
+ private fun triggerSettingChange(selfChange: Boolean = false) {
+ verify(contentResolver)
+ .registerContentObserver(any(), anyBoolean(), contentObserver.capture())
+ contentObserver.value.onChange(selfChange)
+ }
+
private fun triggerEvent(event: AudioManagerEvent) {
testScope.launch { eventsReceiver.triggerEvent(event) }
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 374240b..04d30ed 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -610,6 +610,8 @@
<!-- Permission required for CTS test - CtsThreadNetworkTestCases -->
<uses-permission android:name="android.permission.THREAD_NETWORK_PRIVILEGED"/>
+ <!-- Permission required to access Thread network shell commands for testing -->
+ <uses-permission android:name="android.permission.THREAD_NETWORK_TESTING"/>
<!-- Permission required for CTS tests to enable/disable rate limiting toasts. -->
<uses-permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING" />
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
index ee81813..0dd9275 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -36,6 +36,7 @@
import android.os.UserManager;
import android.provider.MediaStore;
import android.provider.Settings;
+import android.text.Html;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
@@ -253,6 +254,9 @@
} else {
p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title);
}
+ } else {
+ // Make sure intents don't inject HTML elements.
+ p.mTitle = Html.escapeHtml(p.mTitle.toString());
}
setupAlert();
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index ac680a9..e940674 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -752,6 +752,7 @@
plugins: [
"dagger2-compiler",
],
+ strict_mode: false,
}
// in-place tests which use Robolectric in the tests directory
@@ -771,7 +772,6 @@
],
static_libs: [
"RoboTestLibraries",
- "mockito-kotlin2",
],
libs: [
"android.test.runner",
@@ -787,6 +787,7 @@
plugins: [
"dagger2-compiler",
],
+ strict_mode: false,
}
android_ravenwood_test {
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 163c849..36bad5e 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -960,10 +960,13 @@
}
flag {
- name: "media_controls_user_initiated_dismiss"
+ name: "media_controls_user_initiated_deleteintent"
namespace: "systemui"
description: "Only dismiss media notifications when the control was removed by the user."
bug: "335875159"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
index f5d01d7..907c39d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
@@ -944,9 +944,26 @@
}
override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
- startController.onTransitionAnimationEnd(isExpandingFullyAbove)
- endController.onTransitionAnimationEnd(isExpandingFullyAbove)
- onLaunchAnimationEnd()
+ // onLaunchAnimationEnd is called by an Animator at the end of the animation,
+ // on a Choreographer animation tick. The following calls will move the animated
+ // content from the dialog overlay back to its original position, and this
+ // change must be reflected in the next frame given that we then sync the next
+ // frame of both the content and dialog ViewRoots. However, in case that content
+ // is rendered by Compose, whose compositions are also scheduled on a
+ // Choreographer frame, any state change made *right now* won't be reflected in
+ // the next frame given that a Choreographer frame can't schedule another and
+ // have it happen in the same frame. So we post the forwarded calls to
+ // [Controller.onLaunchAnimationEnd], leaving this Choreographer frame, ensuring
+ // that the move of the content back to its original window will be reflected in
+ // the next frame right after [onLaunchAnimationEnd] is called.
+ //
+ // TODO(b/330672236): Move this to TransitionAnimator.
+ dialog.context.mainExecutor.execute {
+ startController.onTransitionAnimationEnd(isExpandingFullyAbove)
+ endController.onTransitionAnimationEnd(isExpandingFullyAbove)
+
+ onLaunchAnimationEnd()
+ }
}
override fun onTransitionAnimationProgress(
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index cc55df1..8e824e6 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -357,26 +357,13 @@
Log.d(TAG, "Animation ended")
}
- // onAnimationEnd is called at the end of the animation, on a Choreographer
- // animation tick. During dialog launches, the following calls will move the
- // animated content from the dialog overlay back to its original position, and
- // this change must be reflected in the next frame given that we then sync the
- // next frame of both the content and dialog ViewRoots. During SysUI activity
- // launches, we will instantly collapse the shade at the end of the transition.
- // However, if those are rendered by Compose, whose compositions are also
- // scheduled on a Choreographer frame, any state change made *right now* won't
- // be reflected in the next frame given that a Choreographer frame can't
- // schedule another and have it happen in the same frame. So we post the
- // forwarded calls to [Controller.onLaunchAnimationEnd] in the main executor,
- // leaving this Choreographer frame, ensuring that any state change applied by
- // onTransitionAnimationEnd() will be reflected in the same frame.
- mainExecutor.execute {
- controller.onTransitionAnimationEnd(isExpandingFullyAbove)
- transitionContainerOverlay.remove(windowBackgroundLayer)
+ // TODO(b/330672236): Post this to the main thread instead so that it does not
+ // flicker with Flexiglass enabled.
+ controller.onTransitionAnimationEnd(isExpandingFullyAbove)
+ transitionContainerOverlay.remove(windowBackgroundLayer)
- if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
- openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
- }
+ if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
+ openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
}
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
index b057296..536f297 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
@@ -73,3 +73,11 @@
maxMarginYdp = 8f,
minScale = 0.9f,
)
+
+/**
+ * SysUI transitions - Bottomsheet (AT3)
+ * https://carbon.googleplex.com/predictive-back-for-apps/pages/at-3-bottom-sheets
+ */
+fun BackAnimationSpec.Companion.bottomSheetForSysUi(
+ displayMetricsProvider: () -> DisplayMetrics,
+): BackAnimationSpec = BackAnimationSpec.createBottomsheetAnimationSpec(displayMetricsProvider)
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt
index 49d1fb4..029f62c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt
@@ -26,12 +26,36 @@
var translateX: Float = Float.NaN,
var translateY: Float = Float.NaN,
var scale: Float = Float.NaN,
+ var scalePivotPosition: ScalePivotPosition? = null,
)
+/** Enum that describes the location of the scale pivot position */
+enum class ScalePivotPosition {
+ // more options may be added in the future
+ CENTER,
+ BOTTOM_CENTER;
+
+ fun applyTo(view: View) {
+ val pivotX =
+ when (this) {
+ CENTER -> view.width / 2f
+ BOTTOM_CENTER -> view.width / 2f
+ }
+ val pivotY =
+ when (this) {
+ CENTER -> view.height / 2f
+ BOTTOM_CENTER -> view.height.toFloat()
+ }
+ view.pivotX = pivotX
+ view.pivotY = pivotY
+ }
+}
+
/** Apply the transformation to the [targetView] */
fun BackTransformation.applyTo(targetView: View) {
if (translateX.isFinite()) targetView.translationX = translateX
if (translateY.isFinite()) targetView.translationY = translateY
+ scalePivotPosition?.applyTo(targetView)
if (scale.isFinite()) {
targetView.scaleX = scale
targetView.scaleY = scale
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BottomsheetBackAnimationSpec.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BottomsheetBackAnimationSpec.kt
new file mode 100644
index 0000000..b1945a1
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BottomsheetBackAnimationSpec.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.animation.back
+
+import android.util.DisplayMetrics
+import android.view.animation.Interpolator
+import com.android.app.animation.Interpolators
+import com.android.systemui.util.dpToPx
+
+private const val MAX_SCALE_DELTA_DP = 48
+
+/** Create a [BackAnimationSpec] from [displayMetrics] and design specs. */
+fun BackAnimationSpec.Companion.createBottomsheetAnimationSpec(
+ displayMetricsProvider: () -> DisplayMetrics,
+ scaleEasing: Interpolator = Interpolators.BACK_GESTURE,
+): BackAnimationSpec {
+ return BackAnimationSpec { backEvent, _, result ->
+ val displayMetrics = displayMetricsProvider()
+ val screenWidthPx = displayMetrics.widthPixels
+ val minScale = 1 - MAX_SCALE_DELTA_DP.dpToPx(displayMetrics) / screenWidthPx
+ val progressX = backEvent.progress
+ val ratioScale = scaleEasing.getInterpolation(progressX)
+ result.apply {
+ scale = 1f - ratioScale * (1f - minScale)
+ scalePivotPosition = ScalePivotPosition.BOTTOM_CENTER
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index 99f7d0f..887e349 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -28,7 +28,6 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import javax.inject.Inject
/**
* Renders the content of the lockscreen.
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 cf2e895..e6132c6 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
@@ -169,6 +169,7 @@
viewModel: NotificationsPlaceholderViewModel,
maxScrimTop: () -> Float,
shouldPunchHoleBehindScrim: Boolean,
+ shouldFillMaxSize: Boolean = true,
shadeMode: ShadeMode,
modifier: Modifier = Modifier,
) {
@@ -327,14 +328,14 @@
}
Box(
modifier =
- Modifier.fillMaxSize()
- .graphicsLayer {
+ Modifier.graphicsLayer {
alpha =
if (shouldPunchHoleBehindScrim) {
(expansionFraction / EXPANSION_FOR_MAX_SCRIM_ALPHA).coerceAtMost(1f)
} else 1f
}
.background(MaterialTheme.colorScheme.surface)
+ .thenIf(shouldFillMaxSize) { Modifier.fillMaxSize() }
.debugBackground(viewModel, DEBUG_BOX_COLOR)
) {
NotificationPlaceholder(
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 e17a146..ae53d56 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
@@ -17,21 +17,31 @@
package com.android.systemui.notifications.ui.composable
import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Text
+import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
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.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.composable.LockscreenContent
import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneViewModel
+import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.shade.shared.model.ShadeMode
+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.notification.stack.ui.view.NotificationScrollView
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+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,36 +51,53 @@
class NotificationsShadeScene
@Inject
constructor(
- viewModel: NotificationsShadeSceneViewModel,
+ sceneViewModel: NotificationsShadeSceneViewModel,
private val overlayShadeViewModel: OverlayShadeViewModel,
+ private val shadeHeaderViewModel: ShadeHeaderViewModel,
+ private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
+ private val tintedIconManagerFactory: TintedIconManager.Factory,
+ private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
+ private val statusBarIconController: StatusBarIconController,
+ private val shadeSession: SaveableSession,
+ private val stackScrollView: Lazy<NotificationScrollView>,
private val lockscreenContent: Lazy<Optional<LockscreenContent>>,
) : ComposableScene {
override val key = Scenes.NotificationsShade
override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- viewModel.destinationScenes
+ sceneViewModel.destinationScenes
@Composable
override fun SceneScope.Content(
modifier: Modifier,
) {
OverlayShade(
- viewModel = overlayShadeViewModel,
modifier = modifier,
+ viewModel = overlayShadeViewModel,
horizontalArrangement = Arrangement.Start,
lockscreenContent = lockscreenContent,
) {
- Text(
- text = "Notifications list",
- modifier = Modifier.padding(NotificationsShade.Dimensions.Padding)
- )
- }
- }
-}
+ Column {
+ ExpandedShadeHeader(
+ viewModel = shadeHeaderViewModel,
+ createTintedIconManager = tintedIconManagerFactory::create,
+ createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
+ statusBarIconController = statusBarIconController,
+ modifier = Modifier.padding(horizontal = 16.dp),
+ )
-object NotificationsShade {
- object Dimensions {
- val Padding = 16.dp
+ NotificationScrollingStack(
+ shadeSession = shadeSession,
+ stackScrollView = stackScrollView.get(),
+ viewModel = notificationsPlaceholderViewModel,
+ maxScrimTop = { 0f },
+ shouldPunchHoleBehindScrim = false,
+ shouldFillMaxSize = false,
+ shadeMode = ShadeMode.Dual,
+ modifier = Modifier.width(416.dp),
+ )
+ }
+ }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index 54a98dd..8195df3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -16,6 +16,8 @@
package com.android.systemui.qs.ui.composable
+import android.view.ViewGroup
+import android.widget.FrameLayout
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
@@ -24,6 +26,7 @@
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
@@ -164,11 +167,8 @@
state: () -> QSSceneAdapter.State,
modifier: Modifier = Modifier,
) {
- val qsView by qsSceneAdapter.qsView.collectAsStateWithLifecycle(null)
- val isCustomizing by
- qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle(
- qsSceneAdapter.isCustomizerShowing.value
- )
+ val qsView by qsSceneAdapter.qsView.collectAsStateWithLifecycle()
+ val isCustomizing by qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle()
QuickSettingsTheme {
val context = LocalContext.current
@@ -180,15 +180,34 @@
qsView?.let { view ->
Box(
modifier =
- modifier.fillMaxWidth().thenIf(isCustomizing) { Modifier.fillMaxHeight() }
+ modifier
+ .fillMaxWidth()
+ .thenIf(isCustomizing) { Modifier.fillMaxHeight() }
+ .drawWithContent {
+ qsSceneAdapter.applyLatestExpansionAndSquishiness()
+ drawContent()
+ }
) {
AndroidView(
modifier = Modifier.fillMaxWidth(),
- factory = { _ ->
+ factory = { context ->
qsSceneAdapter.setState(state())
- view
+ FrameLayout(context).apply {
+ (view.parent as? ViewGroup)?.removeView(view)
+ addView(view)
+ }
},
- update = { qsSceneAdapter.setState(state()) }
+ // When the view changes (e.g. due to a theme change), this will be recomposed
+ // if needed and the new view will be attached to the FrameLayout here.
+ update = {
+ qsSceneAdapter.setState(state())
+ if (view.parent != it) {
+ it.removeAllViews()
+ (view.parent as? ViewGroup)?.removeView(view)
+ it.addView(view)
+ }
+ },
+ onRelease = { it.removeAllViews() }
)
}
}
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 04f76f5..4d946bf 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
@@ -33,9 +33,9 @@
import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
import dagger.Lazy
+import java.util.Optional
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow
-import java.util.Optional
@SysUISingleton
class QuickSettingsShadeScene
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 92b2b4e..18e65508 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -30,9 +30,6 @@
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.pointer.PointerEventPass
-import androidx.compose.ui.input.pointer.motionEventSpy
-import androidx.compose.ui.input.pointer.pointerInput
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.SceneKey
@@ -94,21 +91,7 @@
Box(
modifier = Modifier.fillMaxSize(),
) {
- SceneTransitionLayout(
- state = state,
- modifier =
- modifier
- .fillMaxSize()
- .motionEventSpy { event -> viewModel.onMotionEvent(event) }
- .pointerInput(Unit) {
- awaitPointerEventScope {
- while (true) {
- awaitPointerEvent(PointerEventPass.Final)
- viewModel.onMotionEventComplete()
- }
- }
- }
- ) {
+ SceneTransitionLayout(state = state, modifier = modifier.fillMaxSize()) {
sceneByKey.forEach { (sceneKey, composableScene) ->
scene(
key = sceneKey,
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 cbaa894..f5a0ef2 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
@@ -8,12 +8,16 @@
import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse
import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
import com.android.systemui.scene.ui.composable.transitions.bouncerToGoneTransition
+import com.android.systemui.scene.ui.composable.transitions.goneToNotificationsShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsShadeTransition
import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition
import com.android.systemui.scene.ui.composable.transitions.goneToShadeTransition
import com.android.systemui.scene.ui.composable.transitions.goneToSplitShadeTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToBouncerTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToCommunalTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToGoneTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToNotificationsShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsShadeTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToSplitShadeTransition
@@ -37,6 +41,7 @@
// Scene transitions
from(Scenes.Bouncer, to = Scenes.Gone) { bouncerToGoneTransition() }
+ from(Scenes.Gone, to = Scenes.NotificationsShade) { goneToNotificationsShadeTransition() }
from(Scenes.Gone, to = Scenes.Shade) { goneToShadeTransition() }
from(
Scenes.Gone,
@@ -53,8 +58,15 @@
goneToShadeTransition(durationScale = 0.9)
}
from(Scenes.Gone, to = Scenes.QuickSettings) { goneToQuickSettingsTransition() }
+ from(Scenes.Gone, to = Scenes.QuickSettingsShade) { goneToQuickSettingsShadeTransition() }
from(Scenes.Lockscreen, to = Scenes.Bouncer) { lockscreenToBouncerTransition() }
from(Scenes.Lockscreen, to = Scenes.Communal) { lockscreenToCommunalTransition() }
+ from(Scenes.Lockscreen, to = Scenes.NotificationsShade) {
+ lockscreenToNotificationsShadeTransition()
+ }
+ from(Scenes.Lockscreen, to = Scenes.QuickSettingsShade) {
+ lockscreenToQuickSettingsShadeTransition()
+ }
from(Scenes.Lockscreen, to = Scenes.Shade) { lockscreenToShadeTransition() }
from(
Scenes.Lockscreen,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt
new file mode 100644
index 0000000..48ec198
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.composable.transitions
+
+import com.android.compose.animation.scene.TransitionBuilder
+
+fun TransitionBuilder.goneToNotificationsShadeTransition(
+ durationScale: Double = 1.0,
+) {
+ toNotificationsShadeTransition(durationScale)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsShadeTransition.kt
new file mode 100644
index 0000000..225ca4e
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsShadeTransition.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.composable.transitions
+
+import com.android.compose.animation.scene.TransitionBuilder
+
+fun TransitionBuilder.goneToQuickSettingsShadeTransition(
+ durationScale: Double = 1.0,
+) {
+ toQuickSettingsShadeTransition(durationScale)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToNotificationsShadeTransition.kt
new file mode 100644
index 0000000..372e4a1a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToNotificationsShadeTransition.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.composable.transitions
+
+import com.android.compose.animation.scene.TransitionBuilder
+
+fun TransitionBuilder.lockscreenToNotificationsShadeTransition(
+ durationScale: Double = 1.0,
+) {
+ toNotificationsShadeTransition(durationScale)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsShadeTransition.kt
new file mode 100644
index 0000000..ce24f5e
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsShadeTransition.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.composable.transitions
+
+import com.android.compose.animation.scene.TransitionBuilder
+
+fun TransitionBuilder.lockscreenToQuickSettingsShadeTransition(
+ durationScale: Double = 1.0,
+) {
+ toQuickSettingsShadeTransition(durationScale)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
new file mode 100644
index 0000000..a6b268d
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.unit.IntSize
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.UserActionDistance
+import com.android.compose.animation.scene.UserActionDistanceScope
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.shade.ui.composable.OverlayShade
+import com.android.systemui.shade.ui.composable.Shade
+import com.android.systemui.shade.ui.composable.ShadeHeader
+import kotlin.time.Duration.Companion.milliseconds
+
+fun TransitionBuilder.toNotificationsShadeTransition(
+ durationScale: Double = 1.0,
+) {
+ spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
+ swipeSpec =
+ spring(
+ stiffness = Spring.StiffnessMediumLow,
+ visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
+ )
+ distance =
+ object : UserActionDistance {
+ override fun UserActionDistanceScope.absoluteDistance(
+ fromSceneSize: IntSize,
+ orientation: Orientation,
+ ): Float {
+ return fromSceneSize.height.toFloat() * 2 / 3f
+ }
+ }
+
+ translate(OverlayShade.Elements.PanelBackground, Edge.Top)
+ translate(Notifications.Elements.NotificationScrim, Edge.Top)
+
+ fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) }
+
+ fractionRange(start = .5f) {
+ fade(ShadeHeader.Elements.Clock)
+ fade(ShadeHeader.Elements.ExpandedContent)
+ fade(ShadeHeader.Elements.PrivacyChip)
+ fade(Notifications.Elements.NotificationScrim)
+ }
+}
+
+private val DefaultDuration = 300.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
new file mode 100644
index 0000000..2baaecf
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.unit.IntSize
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.UserActionDistance
+import com.android.compose.animation.scene.UserActionDistanceScope
+import com.android.systemui.shade.ui.composable.OverlayShade
+import com.android.systemui.shade.ui.composable.Shade
+import kotlin.time.Duration.Companion.milliseconds
+
+fun TransitionBuilder.toQuickSettingsShadeTransition(
+ durationScale: Double = 1.0,
+) {
+ spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
+ swipeSpec =
+ spring(
+ stiffness = Spring.StiffnessMediumLow,
+ visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
+ )
+ distance =
+ object : UserActionDistance {
+ override fun UserActionDistanceScope.absoluteDistance(
+ fromSceneSize: IntSize,
+ orientation: Orientation,
+ ): Float {
+ return fromSceneSize.height.toFloat() * 2 / 3f
+ }
+ }
+
+ translate(OverlayShade.Elements.PanelBackground, Edge.Top)
+
+ fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) }
+}
+
+private val DefaultDuration = 300.milliseconds
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 2f11085..34cc676 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
@@ -23,9 +23,12 @@
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.getValue
@@ -37,6 +40,8 @@
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
import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
@@ -54,6 +59,8 @@
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) {
@@ -66,10 +73,13 @@
Scrim(onClicked = viewModel::onScrimClicked)
Row(
- modifier = Modifier.fillMaxSize().padding(OverlayShade.Dimensions.ScrimContentPadding),
+ modifier =
+ Modifier.fillMaxSize().thenIf(!isPanelFullWidth) {
+ Modifier.padding(OverlayShade.Dimensions.ScrimContentPadding)
+ },
horizontalArrangement = horizontalArrangement,
) {
- Panel(content = content)
+ Panel(modifier = Modifier.panelSize(), content = content)
}
}
}
@@ -111,6 +121,20 @@
}
}
+@Composable
+private fun Modifier.panelSize(): Modifier {
+ val widthSizeClass = LocalWindowSizeClass.current.widthSizeClass
+
+ return this.then(
+ when (widthSizeClass) {
+ WindowWidthSizeClass.Compact -> Modifier.fillMaxWidth()
+ WindowWidthSizeClass.Medium -> Modifier.width(OverlayShade.Dimensions.PanelWidthMedium)
+ WindowWidthSizeClass.Expanded -> Modifier.width(OverlayShade.Dimensions.PanelWidthLarge)
+ else -> error("Unsupported WindowWidthSizeClass \"$widthSizeClass\"")
+ }
+ )
+}
+
object OverlayShade {
object Elements {
val Scrim = ElementKey("OverlayShadeScrim", scenePicker = LowestZIndexScenePicker)
@@ -127,6 +151,8 @@
object Dimensions {
val ScrimContentPadding = 16.dp
val PanelCornerRadius = 46.dp
+ val PanelWidthMedium = 390.dp
+ val PanelWidthLarge = 474.dp
}
object Shapes {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 9d689fc..33a630c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -25,6 +25,7 @@
import androidx.compose.foundation.clipScrollableContainer
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -42,6 +43,7 @@
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
@@ -67,6 +69,7 @@
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.compose.modifiers.padding
import com.android.compose.modifiers.thenIf
+import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
@@ -235,6 +238,10 @@
val shouldPunchHoleBehindScrim =
layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade) ||
layoutState.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade)
+ // Media is visible and we are in landscape on a small height screen
+ val mediaInRow =
+ isMediaVisible &&
+ LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact
Box(
modifier =
@@ -274,22 +281,39 @@
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
)
- Box(Modifier.element(QuickSettings.Elements.QuickQuickSettings)) {
- QuickSettings(
- viewModel.qsSceneAdapter,
- { viewModel.qsSceneAdapter.qqsHeight },
- isSplitShade = false,
- squishiness = { tileSquishiness },
+
+ val content: @Composable (Modifier) -> Unit = { modifier ->
+ Box(
+ Modifier.element(QuickSettings.Elements.QuickQuickSettings)
+ .then(modifier)
+ ) {
+ QuickSettings(
+ viewModel.qsSceneAdapter,
+ { viewModel.qsSceneAdapter.qqsHeight },
+ isSplitShade = false,
+ squishiness = { tileSquishiness },
+ )
+ }
+
+ MediaCarousel(
+ isVisible = isMediaVisible,
+ mediaHost = mediaHost,
+ modifier = Modifier.fillMaxWidth().then(modifier),
+ carouselController = mediaCarouselController,
)
}
- MediaCarousel(
- isVisible = isMediaVisible,
- mediaHost = mediaHost,
- modifier = Modifier.fillMaxWidth(),
- carouselController = mediaCarouselController,
- )
-
+ if (!mediaInRow) {
+ content(Modifier)
+ } else {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = spacedBy(16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ content(Modifier.weight(1f))
+ }
+ }
Spacer(modifier = Modifier.height(16.dp))
}
},
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/EdgeToEdgeDialogDelegate.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/EdgeToEdgeDialogDelegate.kt
index 55dfed4..5fc78c0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/EdgeToEdgeDialogDelegate.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/EdgeToEdgeDialogDelegate.kt
@@ -17,8 +17,11 @@
package com.android.systemui.statusbar.phone
import android.os.Bundle
+import android.util.DisplayMetrics
import android.view.Gravity
import android.view.WindowManager
+import com.android.systemui.animation.back.BackAnimationSpec
+import com.android.systemui.animation.back.bottomSheetForSysUi
/** [DialogDelegate] that configures a dialog to be an edge-to-edge one. */
class EdgeToEdgeDialogDelegate : DialogDelegate<SystemUIDialog> {
@@ -40,4 +43,10 @@
override fun getWidth(dialog: SystemUIDialog): Int = WindowManager.LayoutParams.MATCH_PARENT
override fun getHeight(dialog: SystemUIDialog): Int = WindowManager.LayoutParams.MATCH_PARENT
+
+ override fun getBackAnimationSpec(
+ displayMetricsProvider: () -> DisplayMetrics
+ ): BackAnimationSpec {
+ return BackAnimationSpec.bottomSheetForSysUi(displayMetricsProvider)
+ }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
index 83b8158..ab14911 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
@@ -23,7 +23,6 @@
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -43,16 +42,7 @@
fun VolumePanelRoot(
viewModel: VolumePanelViewModel,
modifier: Modifier = Modifier,
- onDismiss: () -> Unit
) {
- LaunchedEffect(viewModel) {
- viewModel.volumePanelState.collect {
- if (!it.isVisible) {
- onDismiss()
- }
- }
- }
-
val accessibilityTitle = stringResource(R.string.accessibility_volume_settings)
val state: VolumePanelState by viewModel.volumePanelState.collectAsStateWithLifecycle()
val components by viewModel.componentsLayout.collectAsStateWithLifecycle(null)
diff --git a/packages/SystemUI/docs/demo_mode.md b/packages/SystemUI/docs/demo_mode.md
index b2424f4..ade5171 100644
--- a/packages/SystemUI/docs/demo_mode.md
+++ b/packages/SystemUI/docs/demo_mode.md
@@ -22,42 +22,45 @@
<br/>
Commands are sent as string extras with key ```command``` (required). Possible values are:
-| Command | Subcommand | Argument | Description
-| --- | --- | --- | ---
-| ```enter``` | | | Enters demo mode, bar state allowed to be modified (for convenience, any of the other non-exit commands will automatically flip demo mode on, no need to call this explicitly in practice)
-| ```exit``` | | | Exits demo mode, bars back to their system-driven state
-| ```battery``` | | | Control the battery display
-| | ```level``` | | Sets the battery level (0 - 100)
-| | ```plugged``` | | Sets charging state (```true```, ```false```)
-| | ```powersave``` | | Sets power save mode (```true```, ```anything else```)
-| ```network``` | | | Control the RSSI display
-| | ```airplane``` | | ```show``` to show icon, any other value to hide
-| | ```fully``` | | Sets MCS state to fully connected (```true```, ```false```)
-| | ```wifi``` | | ```show``` to show icon, any other value to hide
-| | | ```level``` | Sets wifi level (null or 0-4)
-| | ```mobile``` | | ```show``` to show icon, any other value to hide
-| | | ```datatype``` | Values: ```1x```, ```3g```, ```4g```, ```e```, ```g```, ```h```, ```lte```, ```roam```, any other value to hide
-| | | ```level``` | Sets mobile signal strength level (null or 0-4)
-| | ```carriernetworkchange``` | | Sets mobile signal icon to carrier network change UX when disconnected (```show``` to show icon, any other value to hide)
-| | ```sims``` | | Sets the number of sims (1-8)
-| | ```nosim``` | | ```show``` to show icon, any other value to hide
-| ```bars``` | | | Control the visual style of the bars (opaque, translucent, etc)
-| | ```mode``` | | Sets the bars visual style (opaque, translucent, semi-transparent)
-| ```status``` | | | Control the system status icons
-| | ```volume``` | | Sets the icon in the volume slot (```silent```, ```vibrate```, any other value to hide)
-| | ```bluetooth``` | | Sets the icon in the bluetooth slot (```connected```, ```disconnected```, any other value to hide)
-| | ```location``` | | Sets the icon in the location slot (```show```, any other value to hide)
-| | ```alarm``` | | Sets the icon in the alarm_clock slot (```show```, any other value to hide)
-| | ```sync``` | | Sets the icon in the sync_active slot (```show```, any other value to hide)
-| | ```tty``` | | Sets the icon in the tty slot (```show```, any other value to hide)
-| | ```eri``` | | Sets the icon in the cdma_eri slot (```show```, any other value to hide)
-| | ```mute``` | | Sets the icon in the mute slot (```show```, any other value to hide)
-| | ```speakerphone``` | | Sets the icon in the speakerphone slot (```show```, any other value to hide)
-| ```notifications``` | | | Control the notification icons
-| | ```visible``` | | ```false``` to hide the notification icons, any other value to show
-| ```clock``` | | | Control the clock display
-| | ```millis``` | | Sets the time in millis
-| | ```hhmm``` | | Sets the time in hh:mm
+| Command | Subcommand | Argument | Description
+| --- |----------------------------|------------------| ---
+| ```enter``` | | | Enters demo mode, bar state allowed to be modified (for convenience, any of the other non-exit commands will automatically flip demo mode on, no need to call this explicitly in practice)
+| ```exit``` | | | Exits demo mode, bars back to their system-driven state
+| ```battery``` | | | Control the battery display
+| | ```level``` | | Sets the battery level (0 - 100)
+| | ```plugged``` | | Sets charging state (```true```, ```false```)
+| | ```powersave``` | | Sets power save mode (```true```, ```anything else```)
+| ```network``` | | | Control the RSSI display
+| | ```airplane``` | | ```show``` to show icon, any other value to hide
+| | ```fully``` | | Sets MCS state to fully connected (```true```, ```false```)
+| | ```wifi``` | | ```show``` to show icon, any other value to hide
+| | | ```level``` | Sets wifi level (null or 0-4)
+| | ```mobile``` | | ```show``` to show icon, any other value to hide
+| | | ```datatype``` | Values: ```1x```, ```3g```, ```4g```, ```e```, ```g```, ```h```, ```lte```, ```roam```, any other value to hide
+| | | ```level``` | Sets mobile signal strength level (null or 0-4)
+| | ```satellite``` | | ```show``` to show icon, any other value to hide
+| | | ```connection``` | ```connected```, ```off```, ```on```, or ```unknown``` (matches SatelliteConnectionState enum)
+| | | ```level``` | Sets satellite signal strength level (0-4)
+| | ```carriernetworkchange``` | | Sets mobile signal icon to carrier network change UX when disconnected (```show``` to show icon, any other value to hide)
+| | ```sims``` | | Sets the number of sims (1-8)
+| | ```nosim``` | | ```show``` to show icon, any other value to hide
+| ```bars``` | | | Control the visual style of the bars (opaque, translucent, etc)
+| | ```mode``` | | Sets the bars visual style (opaque, translucent, semi-transparent)
+| ```status``` | | | Control the system status icons
+| | ```volume``` | | Sets the icon in the volume slot (```silent```, ```vibrate```, any other value to hide)
+| | ```bluetooth``` | | Sets the icon in the bluetooth slot (```connected```, ```disconnected```, any other value to hide)
+| | ```location``` | | Sets the icon in the location slot (```show```, any other value to hide)
+| | ```alarm``` | | Sets the icon in the alarm_clock slot (```show```, any other value to hide)
+| | ```sync``` | | Sets the icon in the sync_active slot (```show```, any other value to hide)
+| | ```tty``` | | Sets the icon in the tty slot (```show```, any other value to hide)
+| | ```eri``` | | Sets the icon in the cdma_eri slot (```show```, any other value to hide)
+| | ```mute``` | | Sets the icon in the mute slot (```show```, any other value to hide)
+| | ```speakerphone``` | | Sets the icon in the speakerphone slot (```show```, any other value to hide)
+| ```notifications``` | | | Control the notification icons
+| | ```visible``` | | ```false``` to hide the notification icons, any other value to show
+| ```clock``` | | | Control the clock display
+| | ```millis``` | | Sets the time in millis
+| | ```hhmm``` | | Sets the time in hh:mm
## Examples
Enter demo mode
@@ -90,6 +93,15 @@
```
+Show the satellite icon
+
+```
+# Sets mobile to be out-of-service, which is required for satellite to show
+adb shell am broadcast -a com.android.systemui.demo -e command network -e mobile show -e level 0
+# Sets satellite to be connected
+adb shell am broadcast -a com.android.systemui.demo -e command network -e satellite show -e level 4 -e connection connected
+```
+
Show the silent volume icon
```
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
index d88260f..3035481 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -16,90 +16,93 @@
package com.android.systemui.bouncer.domain.interactor
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
-import com.android.systemui.util.time.SystemClock
-import dagger.Lazy
-import kotlinx.coroutines.test.TestScope
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.statusbar.policy.keyguardStateController
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.mock
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
class AlternateBouncerInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
private lateinit var underTest: AlternateBouncerInteractor
- private lateinit var bouncerRepository: KeyguardBouncerRepository
- private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
- private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
- @Mock private lateinit var statusBarStateController: StatusBarStateController
- @Mock private lateinit var keyguardStateController: KeyguardStateController
- @Mock private lateinit var systemClock: SystemClock
- @Mock private lateinit var bouncerLogger: TableLogBuffer
- @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Before
fun setup() {
- MockitoAnnotations.initMocks(this)
- bouncerRepository =
- KeyguardBouncerRepositoryImpl(
- FakeSystemClock(),
- TestScope().backgroundScope,
- bouncerLogger,
- )
- biometricSettingsRepository = FakeBiometricSettingsRepository()
- fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
-
- mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
- initializeUnderTest()
- }
-
- private fun initializeUnderTest() {
- // Set any feature flags before creating the alternateBouncerInteractor
- underTest =
- AlternateBouncerInteractor(
- statusBarStateController,
- keyguardStateController,
- bouncerRepository,
- fingerprintPropertyRepository,
- biometricSettingsRepository,
- systemClock,
- keyguardUpdateMonitor,
- Lazy { mock(DeviceEntryFingerprintAuthInteractor::class.java) },
- Lazy { mock(KeyguardInteractor::class.java) },
- Lazy { mock(KeyguardTransitionInteractor::class.java) },
- TestScope().backgroundScope,
- )
+ underTest = kosmos.alternateBouncerInteractor
}
@Test(expected = IllegalStateException::class)
+ @EnableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
fun enableUdfpsRefactor_deprecatedShowMethod_throwsIllegalStateException() {
- mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
underTest.show()
}
@Test
+ @DisableSceneContainer
+ fun canShowAlternateBouncer_false_dueToTransitionState() =
+ kosmos.testScope.runTest {
+ givenAlternateBouncerSupported()
+ val canShowAlternateBouncer by collectLastValue(underTest.canShowAlternateBouncer)
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ validateStep = false,
+ )
+ assertFalse(canShowAlternateBouncer!!)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun canShowAlternateBouncer_false_dueToTransitionState_scene_container() =
+ kosmos.testScope.runTest {
+ givenAlternateBouncerSupported()
+ val canShowAlternateBouncer by collectLastValue(underTest.canShowAlternateBouncer)
+ val isDeviceUnlocked by
+ collectLastValue(
+ kosmos.deviceUnlockedInteractor.deviceUnlockStatus.map { it.isUnlocked }
+ )
+ assertThat(isDeviceUnlocked).isFalse()
+
+ kosmos.authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
+ assertThat(isDeviceUnlocked).isTrue()
+ kosmos.sceneInteractor.changeScene(Scenes.Gone, "")
+
+ assertThat(canShowAlternateBouncer).isFalse()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
fun canShowAlternateBouncerForFingerprint_givenCanShow() {
givenCanShowAlternateBouncer()
assertTrue(underTest.canShowAlternateBouncerForFingerprint())
@@ -108,7 +111,7 @@
@Test
fun canShowAlternateBouncerForFingerprint_alternateBouncerUIUnavailable() {
givenCanShowAlternateBouncer()
- bouncerRepository.setAlternateBouncerUIAvailable(false)
+ kosmos.keyguardBouncerRepository.setAlternateBouncerUIAvailable(false)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@@ -116,7 +119,7 @@
@Test
fun canShowAlternateBouncerForFingerprint_ifFingerprintIsNotUsuallyAllowed() {
givenCanShowAlternateBouncer()
- biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@@ -124,7 +127,7 @@
@Test
fun canShowAlternateBouncerForFingerprint_strongBiometricNotAllowed() {
givenCanShowAlternateBouncer()
- biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(false)
+ kosmos.biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(false)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@@ -132,23 +135,24 @@
@Test
fun canShowAlternateBouncerForFingerprint_fingerprintLockedOut() {
givenCanShowAlternateBouncer()
- whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true)
+ whenever(kosmos.keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@Test
+ @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
fun show_whenCanShow() {
givenCanShowAlternateBouncer()
assertTrue(underTest.show())
- assertTrue(bouncerRepository.alternateBouncerVisible.value)
+ assertTrue(kosmos.keyguardBouncerRepository.alternateBouncerVisible.value)
}
@Test
fun canShowAlternateBouncerForFingerprint_butCanDismissLockScreen() {
givenCanShowAlternateBouncer()
- whenever(keyguardStateController.isUnlocked).thenReturn(true)
+ whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(true)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@@ -156,82 +160,86 @@
@Test
fun canShowAlternateBouncerForFingerprint_primaryBouncerShowing() {
givenCanShowAlternateBouncer()
- bouncerRepository.setPrimaryShow(true)
+ kosmos.keyguardBouncerRepository.setPrimaryShow(true)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@Test
+ @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
fun show_whenCannotShow() {
givenCannotShowAlternateBouncer()
assertFalse(underTest.show())
- assertFalse(bouncerRepository.alternateBouncerVisible.value)
+ assertFalse(kosmos.keyguardBouncerRepository.alternateBouncerVisible.value)
}
@Test
fun hide_wasPreviouslyShowing() {
- bouncerRepository.setAlternateVisible(true)
+ kosmos.keyguardBouncerRepository.setAlternateVisible(true)
assertTrue(underTest.hide())
- assertFalse(bouncerRepository.alternateBouncerVisible.value)
+ assertFalse(kosmos.keyguardBouncerRepository.alternateBouncerVisible.value)
}
@Test
fun hide_wasNotPreviouslyShowing() {
- bouncerRepository.setAlternateVisible(false)
+ kosmos.keyguardBouncerRepository.setAlternateVisible(false)
assertFalse(underTest.hide())
- assertFalse(bouncerRepository.alternateBouncerVisible.value)
+ assertFalse(kosmos.keyguardBouncerRepository.alternateBouncerVisible.value)
}
@Test
+ @EnableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
fun canShowAlternateBouncerForFingerprint_rearFps() {
- mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
- initializeUnderTest()
givenCanShowAlternateBouncer()
- fingerprintPropertyRepository.supportsRearFps() // does not support alternate bouncer
+ kosmos.fingerprintPropertyRepository.supportsRearFps() // does not support alternate bouncer
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@Test
+ @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
fun alternateBouncerUiAvailable_fromMultipleSources() {
- initializeUnderTest()
- assertFalse(bouncerRepository.alternateBouncerUIAvailable.value)
+ assertFalse(kosmos.keyguardBouncerRepository.alternateBouncerUIAvailable.value)
// GIVEN there are two different sources indicating the alternate bouncer is available
underTest.setAlternateBouncerUIAvailable(true, "source1")
underTest.setAlternateBouncerUIAvailable(true, "source2")
- assertTrue(bouncerRepository.alternateBouncerUIAvailable.value)
+ assertTrue(kosmos.keyguardBouncerRepository.alternateBouncerUIAvailable.value)
// WHEN one of the sources no longer says the UI is available
underTest.setAlternateBouncerUIAvailable(false, "source1")
// THEN alternate bouncer UI is still available (from the other source)
- assertTrue(bouncerRepository.alternateBouncerUIAvailable.value)
+ assertTrue(kosmos.keyguardBouncerRepository.alternateBouncerUIAvailable.value)
// WHEN all sources say the UI is not available
underTest.setAlternateBouncerUIAvailable(false, "source2")
// THEN alternate boucer UI is not available
- assertFalse(bouncerRepository.alternateBouncerUIAvailable.value)
+ assertFalse(kosmos.keyguardBouncerRepository.alternateBouncerUIAvailable.value)
+ }
+
+ private fun givenAlternateBouncerSupported() {
+ if (DeviceEntryUdfpsRefactor.isEnabled) {
+ kosmos.fingerprintPropertyRepository.supportsUdfps()
+ } else {
+ kosmos.keyguardBouncerRepository.setAlternateBouncerUIAvailable(true)
+ }
}
private fun givenCanShowAlternateBouncer() {
- if (DeviceEntryUdfpsRefactor.isEnabled) {
- fingerprintPropertyRepository.supportsUdfps()
- } else {
- bouncerRepository.setAlternateBouncerUIAvailable(true)
- }
- bouncerRepository.setPrimaryShow(false)
- biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
- biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
- whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
- whenever(keyguardStateController.isUnlocked).thenReturn(false)
+ givenAlternateBouncerSupported()
+ kosmos.keyguardBouncerRepository.setPrimaryShow(false)
+ kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ kosmos.biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+ whenever(kosmos.keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
+ whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
}
private fun givenCannotShowAlternateBouncer() {
- biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
index 2d78a9b..45e7d8a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
@@ -39,7 +39,7 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val underTest by lazy {
- CommunalRepositoryImpl(
+ CommunalSceneRepositoryImpl(
kosmos.applicationCoroutineScope,
kosmos.sceneDataSource,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
index 5a7cbf6..6b896c6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
@@ -21,7 +21,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
import com.android.systemui.communal.data.repository.fakeCommunalRepository
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
@@ -48,7 +48,7 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private lateinit var communalRepository: FakeCommunalRepository
+ private lateinit var communalRepository: FakeCommunalSceneRepository
private lateinit var widgetRepository: FakeCommunalWidgetRepository
private lateinit var keyguardRepository: FakeKeyguardRepository
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 83227e1..12389a3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -39,7 +39,7 @@
import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
@@ -48,6 +48,7 @@
import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
@@ -111,7 +112,7 @@
private val testScope = kosmos.testScope
private lateinit var tutorialRepository: FakeCommunalTutorialRepository
- private lateinit var communalRepository: FakeCommunalRepository
+ private lateinit var communalRepository: FakeCommunalSceneRepository
private lateinit var mediaRepository: FakeCommunalMediaRepository
private lateinit var widgetRepository: FakeCommunalWidgetRepository
private lateinit var smartspaceRepository: FakeSmartspaceRepository
@@ -508,30 +509,6 @@
}
@Test
- fun desiredScene_communalNotAvailable_returnsBlank() =
- testScope.runTest {
- kosmos.setCommunalAvailable(true)
- runCurrent()
-
- val desiredScene by collectLastValue(underTest.desiredScene)
-
- underTest.changeScene(CommunalScenes.Communal)
- assertThat(desiredScene).isEqualTo(CommunalScenes.Communal)
-
- kosmos.setCommunalAvailable(false)
- runCurrent()
-
- // Scene returns blank when communal is not available.
- assertThat(desiredScene).isEqualTo(CommunalScenes.Blank)
-
- kosmos.setCommunalAvailable(true)
- runCurrent()
-
- // After re-enabling, scene goes back to Communal.
- assertThat(desiredScene).isEqualTo(CommunalScenes.Communal)
- }
-
- @Test
fun transitionProgress_onTargetScene_fullProgress() =
testScope.runTest {
val targetScene = CommunalScenes.Blank
@@ -545,7 +522,8 @@
underTest.setTransitionState(transitionState)
// We're on the target scene.
- assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(targetScene))
+ assertThat(transitionProgress)
+ .isEqualTo(CommunalTransitionProgressModel.Idle(targetScene))
}
@Test
@@ -563,7 +541,8 @@
underTest.setTransitionState(transitionState)
// Transition progress is still idle, but we're not on the target scene.
- assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(currentScene))
+ assertThat(transitionProgress)
+ .isEqualTo(CommunalTransitionProgressModel.Idle(currentScene))
}
@Test
@@ -581,7 +560,8 @@
underTest.setTransitionState(transitionState)
// Progress starts at 0.
- assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(currentScene))
+ assertThat(transitionProgress)
+ .isEqualTo(CommunalTransitionProgressModel.Idle(currentScene))
val progress = MutableStateFlow(0f)
transitionState =
@@ -599,16 +579,18 @@
// Partially transition.
progress.value = .4f
- assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Transition(.4f))
+ assertThat(transitionProgress)
+ .isEqualTo(CommunalTransitionProgressModel.Transition(.4f))
// Transition is at full progress.
progress.value = 1f
- assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Transition(1f))
+ assertThat(transitionProgress).isEqualTo(CommunalTransitionProgressModel.Transition(1f))
// Transition finishes.
transitionState = MutableStateFlow(ObservableTransitionState.Idle(targetScene))
underTest.setTransitionState(transitionState)
- assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(targetScene))
+ assertThat(transitionProgress)
+ .isEqualTo(CommunalTransitionProgressModel.Idle(targetScene))
}
@Test
@@ -626,7 +608,8 @@
underTest.setTransitionState(transitionState)
// Progress starts at 0.
- assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(currentScene))
+ assertThat(transitionProgress)
+ .isEqualTo(CommunalTransitionProgressModel.Idle(currentScene))
val progress = MutableStateFlow(0f)
transitionState =
@@ -646,16 +629,19 @@
progress.value = .4f
// This is a transition we don't care about the progress of.
- assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.OtherTransition)
+ assertThat(transitionProgress)
+ .isEqualTo(CommunalTransitionProgressModel.OtherTransition)
// Transition is at full progress.
progress.value = 1f
- assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.OtherTransition)
+ assertThat(transitionProgress)
+ .isEqualTo(CommunalTransitionProgressModel.OtherTransition)
// Transition finishes.
transitionState = MutableStateFlow(ObservableTransitionState.Idle(targetScene))
underTest.setTransitionState(transitionState)
- assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(targetScene))
+ assertThat(transitionProgress)
+ .isEqualTo(CommunalTransitionProgressModel.Idle(targetScene))
}
@Test
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 be44339..6b80775 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
@@ -28,7 +28,7 @@
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
@@ -106,7 +106,7 @@
private lateinit var userRepository: FakeUserRepository
private lateinit var shadeTestUtil: ShadeTestUtil
private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
- private lateinit var communalRepository: FakeCommunalRepository
+ private lateinit var communalRepository: FakeCommunalSceneRepository
private lateinit var underTest: CommunalViewModel
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index eef2337..933f095 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -45,7 +45,7 @@
import com.android.systemui.ambient.touch.scrim.ScrimManager
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
import com.android.systemui.communal.data.repository.fakeCommunalRepository
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
@@ -79,7 +79,6 @@
import org.mockito.Mockito
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.isNull
-import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -156,7 +155,7 @@
@Mock lateinit var mDreamOverlayCallbackController: DreamOverlayCallbackController
private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
- private lateinit var communalRepository: FakeCommunalRepository
+ private lateinit var communalRepository: FakeCommunalSceneRepository
@Captor var mViewCaptor: ArgumentCaptor<View>? = null
private lateinit var mService: DreamOverlayService
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModelTest.kt
new file mode 100644
index 0000000..460a1fc
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModelTest.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.ui.viewmodel
+
+import android.graphics.Color
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryForegroundViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest: DeviceEntryForegroundViewModel =
+ kosmos.deviceEntryForegroundIconViewModel
+
+ @Test
+ fun aodIconColorWhite() =
+ testScope.runTest {
+ val viewModel by collectLastValue(underTest.viewModel)
+
+ givenUdfpsEnrolledAndEnabled()
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ )
+
+ assertThat(viewModel?.useAodVariant).isEqualTo(true)
+ assertThat(viewModel?.tint).isEqualTo(Color.WHITE)
+ }
+
+ private fun givenUdfpsEnrolledAndEnabled() {
+ kosmos.fakeFingerprintPropertyRepository.supportsUdfps()
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 20ffa33..33e2cac 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -26,7 +26,7 @@
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.data.repository.communalRepository
+import com.android.systemui.communal.data.repository.communalSceneRepository
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
@@ -71,7 +71,7 @@
private val testScope = kosmos.testScope
private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
- private val communalRepository by lazy { kosmos.communalRepository }
+ private val communalRepository by lazy { kosmos.communalSceneRepository }
private val screenOffAnimationController by lazy { kosmos.screenOffAnimationController }
private val deviceEntryRepository by lazy { kosmos.fakeDeviceEntryRepository }
private val notificationsKeyguardInteractor by lazy { kosmos.notificationsKeyguardInteractor }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
index f1cd0c8..79e4fef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
@@ -179,6 +179,7 @@
val label = context.getString(R.string.status_bar_alarm)
return QSTileState(
{ Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null) },
+ R.drawable.ic_alarm,
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
index 6e9db2c..a0d26c2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
@@ -254,6 +254,7 @@
val label = context.getString(R.string.battery_detail_switch_title)
return QSTileState(
{ Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ iconRes,
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
index d05e98f..ea7b7c5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
@@ -78,6 +78,7 @@
val label = context.getString(R.string.quick_settings_color_correction_label)
return QSTileState(
{ Icon.Loaded(context.getDrawable(R.drawable.ic_qs_color_correction)!!, null) },
+ R.drawable.ic_qs_color_correction,
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
index 3972938..b4ff565 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
@@ -245,6 +245,7 @@
): QSTileState {
return QSTileState(
{ icon?.let { com.android.systemui.common.shared.model.Icon.Loaded(icon, null) } },
+ null,
"test label",
activationState,
"test subtitle",
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
index b7b3fdb..f8e01be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
@@ -66,6 +66,7 @@
null
)
},
+ R.drawable.ic_qs_font_scaling,
context.getString(R.string.quick_settings_font_scaling_label),
QSTileState.ActivationState.ACTIVE,
null,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
index 39755bf..c44836a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
@@ -70,6 +70,7 @@
QSTileState.ActivationState.ACTIVE,
context.getString(R.string.quick_settings_networks_available),
Icon.Loaded(context.getDrawable(wifiRes)!!, contentDescription = null),
+ wifiRes,
context.getString(R.string.quick_settings_internet_label)
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
@@ -96,6 +97,7 @@
context.getDrawable(R.drawable.ic_qs_no_internet_unavailable)!!,
contentDescription = null
),
+ R.drawable.ic_qs_no_internet_unavailable,
context.getString(R.string.quick_settings_networks_unavailable)
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
@@ -105,11 +107,13 @@
activationState: QSTileState.ActivationState,
secondaryLabel: String,
icon: Icon,
+ iconRes: Int,
contentDescription: String,
): QSTileState {
val label = context.getString(R.string.quick_settings_internet_label)
return QSTileState(
{ icon },
+ iconRes,
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
index ccd7ed9..a7bd697 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
@@ -39,9 +39,7 @@
private val colorInversionTileConfig = kosmos.qsColorInversionTileConfig
private val subtitleArrayId =
SubtitleArrayMapping.getSubtitleId(colorInversionTileConfig.tileSpec.spec)
- private val subtitleArray by lazy {
- context.resources.getStringArray(subtitleArrayId)
- }
+ private val subtitleArray by lazy { context.resources.getStringArray(subtitleArrayId) }
// Using lazy (versus =) to make sure we override the right context -- see b/311612168
private val mapper by lazy {
ColorInversionTileMapper(
@@ -93,6 +91,7 @@
val label = context.getString(R.string.quick_settings_inversion_label)
return QSTileState(
{ Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ iconRes,
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
index 5d2e701..75273f2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
@@ -281,21 +281,16 @@
secondaryLabel: String?
): QSTileState {
val label = context.getString(R.string.quick_settings_night_display_label)
-
+ val iconRes =
+ if (activationState == QSTileState.ActivationState.ACTIVE)
+ R.drawable.qs_nightlight_icon_on
+ else R.drawable.qs_nightlight_icon_off
val contentDescription =
if (TextUtils.isEmpty(secondaryLabel)) label
else TextUtils.concat(label, ", ", secondaryLabel)
return QSTileState(
- {
- Icon.Loaded(
- context.getDrawable(
- if (activationState == QSTileState.ActivationState.ACTIVE)
- R.drawable.qs_nightlight_icon_on
- else R.drawable.qs_nightlight_icon_off
- )!!,
- null
- )
- },
+ { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ iconRes,
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
index 7ef020d..3189a9e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
@@ -97,6 +97,7 @@
val label = context.getString(R.string.quick_settings_onehanded_label)
return QSTileState(
{ Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ iconRes,
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
index d26a213..08e5cbe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
@@ -100,6 +100,7 @@
null
)
},
+ com.android.systemui.res.R.drawable.ic_qr_code_scanner,
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
index 10e9bd6..ca30e9c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
@@ -83,17 +83,13 @@
): QSTileState {
val label =
context.getString(com.android.internal.R.string.reduce_bright_colors_feature_name)
+ val iconRes =
+ if (activationState == QSTileState.ActivationState.ACTIVE)
+ R.drawable.qs_extra_dim_icon_on
+ else R.drawable.qs_extra_dim_icon_off
return QSTileState(
- {
- Icon.Loaded(
- context.getDrawable(
- if (activationState == QSTileState.ActivationState.ACTIVE)
- R.drawable.qs_extra_dim_icon_on
- else R.drawable.qs_extra_dim_icon_off
- )!!,
- null
- )
- },
+ { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ iconRes,
label,
activationState,
context.resources
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
index 60c69f4..04ca38f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
@@ -172,6 +172,7 @@
val label = context.getString(R.string.quick_settings_rotation_unlocked_label)
return QSTileState(
{ Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ iconRes,
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
index d162c77..9bb6141 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
@@ -92,6 +92,7 @@
return QSTileState(
{ Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ iconRes,
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
index 31ae9c5..336b566 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
@@ -111,6 +111,7 @@
return QSTileState(
{ Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ iconRes,
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
index 5e7aadc..b08f39b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
@@ -147,6 +147,7 @@
return QSTileState(
{ Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ iconRes,
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
index a977606..c021caa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
@@ -70,6 +70,7 @@
): QSTileState {
return QSTileState(
{ Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ iconRes,
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index afe7b8f..7388d51 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -54,9 +54,11 @@
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@SmallTest
@@ -162,16 +164,25 @@
with(qsImpl!!) {
verify(this).setQsVisible(false)
- verify(this)
+ verify(this, never())
.setQsExpansion(
- /* expansion= */ 0f,
- /* panelExpansionFraction= */ 1f,
- /* proposedTranslation= */ 0f,
- /* squishinessFraction= */ 1f,
+ /* expansion= */ anyFloat(),
+ /* panelExpansionFraction= */ anyFloat(),
+ /* proposedTranslation= */ anyFloat(),
+ /* squishinessFraction= */ anyFloat(),
)
verify(this).setListening(false)
verify(this).setExpanded(false)
}
+
+ underTest.applyLatestExpansionAndSquishiness()
+ verify(qsImpl!!)
+ .setQsExpansion(
+ /* expansion= */ 0f,
+ /* panelExpansionFraction= */ 1f,
+ /* proposedTranslation= */ 0f,
+ /* squishinessFraction= */ 1f,
+ )
}
@Test
@@ -186,16 +197,25 @@
underTest.setState(QSSceneAdapter.State.QQS)
with(qsImpl!!) {
verify(this).setQsVisible(true)
- verify(this)
+ verify(this, never())
.setQsExpansion(
- /* expansion= */ 0f,
- /* panelExpansionFraction= */ 1f,
- /* proposedTranslation= */ 0f,
- /* squishinessFraction= */ 1f,
+ /* expansion= */ anyFloat(),
+ /* panelExpansionFraction= */ anyFloat(),
+ /* proposedTranslation= */ anyFloat(),
+ /* squishinessFraction= */ anyFloat(),
)
verify(this).setListening(true)
verify(this).setExpanded(false)
}
+
+ underTest.applyLatestExpansionAndSquishiness()
+ verify(qsImpl!!)
+ .setQsExpansion(
+ /* expansion= */ 0f,
+ /* panelExpansionFraction= */ 1f,
+ /* proposedTranslation= */ 0f,
+ /* squishinessFraction= */ 1f,
+ )
}
@Test
@@ -210,16 +230,25 @@
underTest.setState(QSSceneAdapter.State.QS)
with(qsImpl!!) {
verify(this).setQsVisible(true)
- verify(this)
+ verify(this, never())
.setQsExpansion(
- /* expansion= */ 1f,
- /* panelExpansionFraction= */ 1f,
- /* proposedTranslation= */ 0f,
- /* squishinessFraction= */ 1f,
+ /* expansion= */ anyFloat(),
+ /* panelExpansionFraction= */ anyFloat(),
+ /* proposedTranslation= */ anyFloat(),
+ /* squishinessFraction= */ anyFloat(),
)
verify(this).setListening(true)
verify(this).setExpanded(true)
}
+
+ underTest.applyLatestExpansionAndSquishiness()
+ verify(qsImpl!!)
+ .setQsExpansion(
+ /* expansion= */ 1f,
+ /* panelExpansionFraction= */ 1f,
+ /* proposedTranslation= */ 0f,
+ /* squishinessFraction= */ 1f,
+ )
}
@Test
@@ -235,16 +264,25 @@
underTest.setState(QSSceneAdapter.State.Expanding(progress))
with(qsImpl!!) {
verify(this).setQsVisible(true)
- verify(this)
+ verify(this, never())
.setQsExpansion(
- /* expansion= */ progress,
- /* panelExpansionFraction= */ 1f,
- /* proposedTranslation= */ 0f,
- /* squishinessFraction= */ 1f,
+ /* expansion= */ anyFloat(),
+ /* panelExpansionFraction= */ anyFloat(),
+ /* proposedTranslation= */ anyFloat(),
+ /* squishinessFraction= */ anyFloat(),
)
verify(this).setListening(true)
verify(this).setExpanded(true)
}
+
+ underTest.applyLatestExpansionAndSquishiness()
+ verify(qsImpl!!)
+ .setQsExpansion(
+ /* expansion= */ progress,
+ /* panelExpansionFraction= */ 1f,
+ /* proposedTranslation= */ 0f,
+ /* squishinessFraction= */ 1f,
+ )
}
@Test
@@ -260,16 +298,25 @@
underTest.setState(QSSceneAdapter.State.UnsquishingQQS { squishiness })
with(qsImpl!!) {
verify(this).setQsVisible(true)
- verify(this)
+ verify(this, never())
.setQsExpansion(
- /* expansion= */ 0f,
- /* panelExpansionFraction= */ 1f,
- /* proposedTranslation= */ 0f,
- /* squishinessFraction= */ squishiness,
+ /* expansion= */ anyFloat(),
+ /* panelExpansionFraction= */ anyFloat(),
+ /* proposedTranslation= */ anyFloat(),
+ /* squishinessFraction= */ anyFloat(),
)
verify(this).setListening(true)
verify(this).setExpanded(false)
}
+
+ underTest.applyLatestExpansionAndSquishiness()
+ verify(qsImpl!!)
+ .setQsExpansion(
+ /* expansion= */ 0f,
+ /* panelExpansionFraction= */ 1f,
+ /* proposedTranslation= */ 0f,
+ /* squishinessFraction= */ squishiness,
+ )
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
index 8cb811d..a67a8ab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
@@ -78,7 +78,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS)
+ @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DELETEINTENT)
fun mediaDataRemoved_userInitiated_dismissNotif() {
val notifEntryCaptor = argumentCaptor<NotificationEntry>()
val notifEntry = mock<NotificationEntry>()
@@ -93,7 +93,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS)
+ @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DELETEINTENT)
fun mediaDataRemoved_notUserInitiated_doesNotDismissNotif() {
listenerCaptor.lastValue.onMediaDataRemoved(KEY, false)
@@ -101,7 +101,7 @@
}
@Test
- @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS)
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DELETEINTENT)
fun mediaDataRemoved_notUserInitiated_flagOff_dismissNotif() {
val notifEntryCaptor = argumentCaptor<NotificationEntry>()
val notifEntry = mock<NotificationEntry>()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
index 288c083..db5921d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
@@ -118,8 +118,6 @@
underTest.setGroupExpanded(summary1, false)
// Expanding again should throw.
- // TODO(b/320238410): Remove this check when robolectric supports wtf assertions.
- Assume.assumeFalse(Build.FINGERPRINT.contains("robolectric"))
assertLogsWtf { underTest.setGroupExpanded(summary1, true) }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
index 1cdf8dc..79b5cc3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
@@ -21,8 +21,10 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.BroadcastReceiver;
import android.content.Intent;
@@ -41,6 +43,7 @@
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.animation.back.BackAnimationSpec;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.model.SysUiState;
@@ -78,6 +81,8 @@
MockitoAnnotations.initMocks(this);
mDependency.injectTestDependency(BroadcastDispatcher.class, mBroadcastDispatcher);
+ when(mDelegate.getBackAnimationSpec(ArgumentMatchers.any()))
+ .thenReturn(mock(BackAnimationSpec.class));
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
index fdea5a4..254a967 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
@@ -24,13 +24,13 @@
import com.android.internal.logging.uiEventLogger
import com.android.internal.logging.uiEventLoggerFake
import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.activityStarter
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
+import com.android.systemui.volume.panel.data.repository.volumePanelGlobalStateRepository
import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import com.android.systemui.volume.panel.ui.viewmodel.volumePanelViewModel
import com.google.common.truth.Truth.assertThat
@@ -56,7 +56,10 @@
@Captor private lateinit var activityStartedCaptor: ArgumentCaptor<ActivityStarter.Callback>
- private val kosmos = testKosmos()
+ private val kosmos =
+ testKosmos().apply {
+ volumePanelGlobalStateRepository.updateVolumePanelState { it.copy(isVisible = true) }
+ }
private lateinit var underTest: BottomBarViewModel
@@ -75,8 +78,7 @@
underTest.onDoneClicked()
runCurrent()
- val volumePanelState by collectLastValue(volumePanelViewModel.volumePanelState)
- assertThat(volumePanelState!!.isVisible).isFalse()
+ assertThat(volumePanelGlobalStateRepository.globalState.value.isVisible).isFalse()
}
}
}
@@ -106,8 +108,7 @@
.isEqualTo(VolumePanelUiEvent.VOLUME_PANEL_SOUND_SETTINGS_CLICKED.id)
activityStartedCaptor.value.onActivityStarted(ActivityManager.START_SUCCESS)
- val volumePanelState by collectLastValue(volumePanelViewModel.volumePanelState)
- assertThat(volumePanelState!!.isVisible).isFalse()
+ assertThat(volumePanelGlobalStateRepository.globalState.value.isVisible).isFalse()
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/VolumePanelGlobalStateInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/VolumePanelGlobalStateInteractorTest.kt
new file mode 100644
index 0000000..d44724f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/VolumePanelGlobalStateInteractorTest.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class VolumePanelGlobalStateInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val underTest = kosmos.volumePanelGlobalStateInteractor
+
+ @Test
+ fun changeVisibility_changesVisibility() =
+ with(kosmos) {
+ testScope.runTest {
+ underTest.setVisible(false)
+ assertThat(underTest.globalState.value.isVisible).isFalse()
+
+ underTest.setVisible(true)
+ assertThat(underTest.globalState.value.isVisible).isTrue()
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
index b37184d..d712afe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
@@ -51,7 +51,7 @@
val component5 = ComponentState(COMPONENT_5, kosmos.mockVolumePanelUiComponent, false)
val layout =
underTest.layout(
- VolumePanelState(0, false, false),
+ VolumePanelState(orientation = 0, isLargeScreen = false),
setOf(
bottomBarComponentState,
component1,
@@ -79,7 +79,7 @@
val component1State = ComponentState(COMPONENT_1, kosmos.mockVolumePanelUiComponent, false)
val component2State = ComponentState(COMPONENT_2, kosmos.mockVolumePanelUiComponent, false)
underTest.layout(
- VolumePanelState(0, false, false),
+ VolumePanelState(orientation = 0, isLargeScreen = false),
setOf(
component1State,
component2State,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
index f6ada4c16..420b955 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.policy.fakeConfigurationController
import com.android.systemui.testKosmos
+import com.android.systemui.volume.panel.data.repository.volumePanelGlobalStateRepository
import com.android.systemui.volume.panel.domain.interactor.criteriaByKey
import com.android.systemui.volume.panel.domain.unavailableCriteria
import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
@@ -49,7 +50,10 @@
class VolumePanelViewModelTest : SysuiTestCase() {
private val kosmos =
- testKosmos().apply { componentsLayoutManager = DefaultComponentsLayoutManager(BOTTOM_BAR) }
+ testKosmos().apply {
+ componentsLayoutManager = DefaultComponentsLayoutManager(BOTTOM_BAR)
+ volumePanelGlobalStateRepository.updateVolumePanelState { it.copy(isVisible = true) }
+ }
private val testableResources = context.orCreateTestableResources
@@ -58,12 +62,10 @@
@Test
fun dismissingPanel_changesVisibility() = test {
testScope.runTest {
- assertThat(underTest.volumePanelState.value.isVisible).isTrue()
-
underTest.dismissPanel()
runCurrent()
- assertThat(underTest.volumePanelState.value.isVisible).isFalse()
+ assertThat(volumePanelGlobalStateRepository.globalState.value.isVisible).isFalse()
}
}
@@ -114,11 +116,10 @@
@Test
fun dismissPanel_dismissesPanel() = test {
testScope.runTest {
- val volumePanelState by collectLastValue(underTest.volumePanelState)
underTest.dismissPanel()
runCurrent()
- assertThat(volumePanelState!!.isVisible).isFalse()
+ assertThat(volumePanelGlobalStateRepository.globalState.value.isVisible).isFalse()
}
}
@@ -126,14 +127,13 @@
fun dismissBroadcast_dismissesPanel() = test {
testScope.runTest {
runCurrent() // run the flows to let allow the receiver to be registered
- val volumePanelState by collectLastValue(underTest.volumePanelState)
broadcastDispatcher.sendIntentToMatchingReceiversOnly(
applicationContext,
Intent(DISMISS_ACTION),
)
runCurrent()
- assertThat(volumePanelState!!.isVisible).isFalse()
+ assertThat(volumePanelGlobalStateRepository.globalState.value.isVisible).isFalse()
}
}
diff --git a/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml b/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml
index 6e6e032..c83b6d3 100644
--- a/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml
+++ b/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml
@@ -30,8 +30,8 @@
android:end="20dp"
android:gravity="end|center_vertical">
<vector
- android:width="@dimen/screenrecord_spinner_arrow_size"
- android:height="@dimen/screenrecord_spinner_arrow_size"
+ android:width="@dimen/hearing_devices_preset_spinner_arrow_size"
+ android:height="@dimen/hearing_devices_preset_spinner_arrow_size"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?androidprv:attr/colorControlNormal">
diff --git a/packages/SystemUI/res/layout/hearing_devices_preset_dropdown_item.xml b/packages/SystemUI/res/layout/hearing_devices_preset_dropdown_item.xml
new file mode 100644
index 0000000..1d9307b
--- /dev/null
+++ b/packages/SystemUI/res/layout/hearing_devices_preset_dropdown_item.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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/hearing_devices_preset_option_text"
+ style="?android:attr/spinnerDropDownItemStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/hearing_devices_preset_spinner_height"
+ android:paddingStart="@dimen/hearing_devices_preset_spinner_text_padding_start"
+ android:gravity="center_vertical"
+ android:ellipsize="end" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/hearing_devices_preset_spinner_selected.xml b/packages/SystemUI/res/layout/hearing_devices_preset_spinner_selected.xml
new file mode 100644
index 0000000..77172ca
--- /dev/null
+++ b/packages/SystemUI/res/layout/hearing_devices_preset_spinner_selected.xml
@@ -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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/hearing_devices_preset_spinner_height"
+ android:paddingStart="@dimen/hearing_devices_preset_spinner_text_padding_start"
+ android:paddingTop="@dimen/hearing_devices_preset_spinner_text_padding_vertical"
+ android:paddingBottom="@dimen/hearing_devices_preset_spinner_text_padding_vertical"
+ android:orientation="vertical">
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:textAppearance="@style/TextAppearance.Dialog.Title"
+ android:lineSpacingExtra="6dp"
+ android:text="@string/hearing_devices_preset_label"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:textSize="14sp"
+ android:gravity="center_vertical"
+ android:layout_weight="1" />
+ <TextView
+ android:id="@+id/hearing_devices_preset_option_text"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:textAppearance="@style/TextAppearance.Dialog.Body"
+ android:lineSpacingExtra="6dp"
+ android:gravity="center_vertical"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:layout_weight="1" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
index 8e1d0a5..2bf6f80 100644
--- a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
@@ -17,7 +17,6 @@
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/root"
style="@style/Widget.SliceView.Panel"
android:layout_width="wrap_content"
@@ -36,16 +35,22 @@
android:id="@+id/preset_spinner"
style="@style/BluetoothTileDialog.Device"
android:layout_width="match_parent"
- android:layout_height="@dimen/hearing_devices_preset_spinner_height"
- android:layout_marginTop="@dimen/hearing_devices_preset_spinner_margin"
- android:layout_marginBottom="@dimen/hearing_devices_preset_spinner_margin"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/hearing_devices_preset_spinner_height"
+ android:layout_marginTop="@dimen/hearing_devices_layout_margin"
+ android:layout_marginBottom="@dimen/hearing_devices_layout_margin"
android:gravity="center_vertical"
android:background="@drawable/hearing_devices_preset_spinner_background"
android:popupBackground="@drawable/hearing_devices_preset_spinner_popup_background"
+ android:dropDownVerticalOffset="@dimen/hearing_devices_preset_spinner_height"
+ android:dropDownWidth="match_parent"
+ android:paddingStart="0dp"
+ android:paddingEnd="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/device_list"
app:layout_constraintBottom_toTopOf="@id/pair_new_device_button"
+ android:longClickable="false"
android:visibility="gone"/>
<androidx.constraintlayout.widget.Barrier
diff --git a/packages/SystemUI/res/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
index 704cf0b..12e226a 100644
--- a/packages/SystemUI/res/layout/window_magnification_settings_view.xml
+++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
@@ -125,6 +125,7 @@
android:ellipsize="marquee"
android:text="@string/accessibility_allow_diagonal_scrolling"
android:textAppearance="@style/TextAppearance.MagnificationSetting.Title"
+ android:labelFor="@id/magnifier_horizontal_lock_switch"
android:layout_gravity="center_vertical" />
<Switch
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 02b74ce..7d7a5d4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1772,10 +1772,10 @@
<dimen name="bluetooth_dialog_scroll_view_min_height_with_auto_on">350dp</dimen>
<!-- Hearing devices dialog related dimensions -->
+ <dimen name="hearing_devices_layout_margin">12dp</dimen>
<dimen name="hearing_devices_preset_spinner_height">72dp</dimen>
- <dimen name="hearing_devices_preset_spinner_margin">24dp</dimen>
<dimen name="hearing_devices_preset_spinner_text_padding_start">20dp</dimen>
- <dimen name="hearing_devices_preset_spinner_text_padding_end">80dp</dimen>
+ <dimen name="hearing_devices_preset_spinner_text_padding_vertical">15dp</dimen>
<dimen name="hearing_devices_preset_spinner_arrow_size">24dp</dimen>
<dimen name="hearing_devices_preset_spinner_background_radius">28dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index abfdc2a..6f2806d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -913,8 +913,10 @@
<string name="quick_settings_pair_hearing_devices">Pair new device</string>
<!-- QuickSettings: Content description of the hearing devices dialog pair new device [CHAR LIMIT=NONE] -->
<string name="accessibility_hearing_device_pair_new_device">Click to pair new device</string>
- <!-- Message when selecting hearing aids presets failed. [CHAR LIMIT=NONE] -->
+ <!-- QuickSettings: Message when selecting hearing aids presets failed. [CHAR LIMIT=NONE] -->
<string name="hearing_devices_presets_error">Couldn\'t update preset</string>
+ <!-- QuickSettings: Title for hearing aids presets. Preset is a set of hearing aid settings. User can apply different settings in different environments (e.g. Outdoor, Restaurant, Home) [CHAR LIMIT=40]-->
+ <string name="hearing_devices_preset_label">Preset</string>
<!--- Title of dialog triggered if the microphone is disabled but an app tried to access it. [CHAR LIMIT=150] -->
<string name="sensor_privacy_start_use_mic_dialog_title">Unblock device microphone?</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index 3f34df7..3bf3fb3 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -300,6 +300,7 @@
});
mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
cancelSatelliteCollectionJob(/* reason= */ "Starting new job");
+ mLogger.logStartListeningForSatelliteCarrierText();
mSatelliteConnectionJob =
mJavaAdapter.alwaysCollectFlow(
mDeviceBasedSatelliteViewModel.getCarrierText(),
@@ -316,7 +317,7 @@
mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
});
mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
- cancelSatelliteCollectionJob(/* reason= */ "Stopping listening");
+ cancelSatelliteCollectionJob(/* reason= */ "#handleSetListening has null callback");
}
}
@@ -336,6 +337,7 @@
private void onSatelliteCarrierTextChanged(@Nullable String text) {
mLogger.logUpdateCarrierTextForReason(REASON_SATELLITE_CHANGED);
+ mLogger.logNewSatelliteCarrierText(text);
mSatelliteCarrierText = text;
updateCarrierText();
}
@@ -654,6 +656,7 @@
private void cancelSatelliteCollectionJob(String reason) {
Job job = mSatelliteConnectionJob;
if (job != null) {
+ mLogger.logStopListeningForSatelliteCarrierText(reason);
job.cancel(new CancellationException(reason));
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
index 48fea55..7d0c491 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
@@ -38,8 +38,11 @@
buffer.log(
TAG,
LogLevel.VERBOSE,
- { int1 = numSubs },
- { "updateCarrierText: location=${location ?: "(unknown)"} numSubs=$int1" },
+ {
+ int1 = numSubs
+ str1 = location
+ },
+ { "updateCarrierText: location=${str1 ?: "(unknown)"} numSubs=$int1" },
)
}
@@ -77,6 +80,15 @@
)
}
+ fun logNewSatelliteCarrierText(newSatelliteText: String?) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ { str1 = newSatelliteText },
+ { "New satellite text = $str1" },
+ )
+ }
+
fun logUsingSatelliteText(satelliteText: String) {
buffer.log(
TAG,
@@ -125,10 +137,37 @@
buffer.log(
TAG,
LogLevel.DEBUG,
- { int1 = reason },
+ {
+ int1 = reason
+ str1 = location
+ },
{
"refreshing carrier info for reason: ${reason.reasonMessage()}" +
- " location=${location ?: "(unknown)"}"
+ " location=${str1 ?: "(unknown)"}"
+ }
+ )
+ }
+
+ fun logStartListeningForSatelliteCarrierText() {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = location },
+ { "Start listening for satellite carrier text. Location=${str1 ?: "(unknown)"}" }
+ )
+ }
+
+ fun logStopListeningForSatelliteCarrierText(reason: String) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = location
+ str2 = reason
+ },
+ {
+ "Stop listening for satellite carrier text. " +
+ "Location=${str1 ?: "(unknown)"} Reason=$str2"
}
)
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index ce4032a..bebfd85 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -16,7 +16,6 @@
package com.android.keyguard.logging
-import android.hardware.biometrics.BiometricSourceType
import com.android.systemui.biometrics.AuthRippleController
import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController
import com.android.systemui.log.LogBuffer
@@ -81,6 +80,23 @@
)
}
+ fun delayShowingTrustAgentError(
+ msg: CharSequence,
+ fpEngaged: Boolean,
+ faceRunning: Boolean,
+ ) {
+ buffer.log(
+ BIO_TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = msg.toString()
+ bool1 = fpEngaged
+ bool2 = faceRunning
+ },
+ { "Delay showing trustAgentError:$str1. fpEngaged:$bool1 faceRunning:$bool2 " }
+ )
+ }
+
fun logUpdateDeviceEntryIndication(
animate: Boolean,
visible: Boolean,
@@ -118,10 +134,9 @@
)
}
- fun logDropNonFingerprintMessage(
+ fun logDropFaceMessage(
message: CharSequence,
followUpMessage: CharSequence?,
- biometricSourceType: BiometricSourceType?,
) {
buffer.log(
KeyguardIndicationController.TAG,
@@ -129,12 +144,8 @@
{
str1 = message.toString()
str2 = followUpMessage?.toString()
- str3 = biometricSourceType?.name
},
- {
- "droppingNonFingerprintMessage message=$str1 " +
- "followUpMessage:$str2 biometricSourceType:$str3"
- }
+ { "droppingFaceMessage message=$str1 followUpMessage:$str2" }
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 7b5a09c..28dd233 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -32,6 +32,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.Visibility;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
@@ -208,6 +209,10 @@
}
mMainHandler.post(() -> {
mDeviceListAdapter.refreshDeviceItemList(mHearingDeviceItemList);
+ final List<BluetoothHapPresetInfo> presetInfos =
+ mPresetsController.getAllPresetInfo();
+ final int activePresetIndex = mPresetsController.getActivePresetIndex();
+ refreshPresetInfoAdapter(presetInfos, activePresetIndex);
mPresetSpinner.setVisibility(
(activeHearingDevice != null && !mPresetInfoAdapter.isEmpty()) ? VISIBLE
: GONE);
@@ -295,10 +300,23 @@
mHearingDeviceItemList);
mPresetsController.setActiveHearingDevice(activeHearingDevice);
- mPresetInfoAdapter = new ArrayAdapter<>(dialog.getContext(),
- android.R.layout.simple_spinner_dropdown_item);
- mPresetInfoAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mPresetInfoAdapter = new ArrayAdapter<String>(dialog.getContext(),
+ R.layout.hearing_devices_preset_spinner_selected,
+ R.id.hearing_devices_preset_option_text);
+ mPresetInfoAdapter.setDropDownViewResource(
+ R.layout.hearing_devices_preset_dropdown_item);
mPresetSpinner.setAdapter(mPresetInfoAdapter);
+
+ // disable redundant Touch & Hold accessibility action for Switch Access
+ mPresetSpinner.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(@NonNull View host,
+ @NonNull AccessibilityNodeInfo info) {
+ info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ }
+ });
+
mPresetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
index 2e29c3b..7503a8b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
@@ -46,15 +46,22 @@
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
- viewModel.shouldHandleTouches.collect { shouldHandleTouches ->
+ viewModel.shouldHandleTouches.collect { shouldHandleTouches ->
+ Log.d(
+ "UdfpsTouchOverlayBinder",
+ "[$view]: update shouldHandleTouches=$shouldHandleTouches"
+ )
+ view.isInvisible = !shouldHandleTouches
+ udfpsOverlayInteractor.setHandleTouches(shouldHandleTouches)
+ }
+ }
+ .invokeOnCompletion {
Log.d(
"UdfpsTouchOverlayBinder",
- "[$view]: update shouldHandleTouches=$shouldHandleTouches"
+ "[$view-detached]: update shouldHandleTouches=false"
)
- view.isInvisible = !shouldHandleTouches
- udfpsOverlayInteractor.setHandleTouches(shouldHandleTouches)
+ udfpsOverlayInteractor.setHandleTouches(false)
}
- }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index 49d0847..51b2280 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -155,7 +155,7 @@
}
}
-internal class AvailableHearingDeviceItemFactory : ActiveMediaDeviceItemFactory() {
+internal class AvailableHearingDeviceItemFactory : AvailableMediaDeviceItemFactory() {
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index e0334a0..1d11dfb 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -28,6 +28,9 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.plugins.statusbar.StatusBarStateController
+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.statusbar.policy.KeyguardStateController
import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
import com.android.systemui.util.time.SystemClock
@@ -60,6 +63,7 @@
private val deviceEntryFingerprintAuthInteractor: Lazy<DeviceEntryFingerprintAuthInteractor>,
private val keyguardInteractor: Lazy<KeyguardInteractor>,
keyguardTransitionInteractor: Lazy<KeyguardTransitionInteractor>,
+ sceneInteractor: Lazy<SceneInteractor>,
@Application scope: CoroutineScope,
) {
var receivedDownTouch = false
@@ -96,30 +100,42 @@
alternateBouncerSupported
.flatMapLatest { alternateBouncerSupported ->
if (alternateBouncerSupported) {
- keyguardTransitionInteractor.get().currentKeyguardState.flatMapLatest {
- currentKeyguardState ->
- if (currentKeyguardState == KeyguardState.GONE) {
- flowOf(false)
- } else {
- combine(
- deviceEntryFingerprintAuthInteractor
- .get()
- .isFingerprintAuthCurrentlyAllowed,
- keyguardInteractor.get().isKeyguardDismissible,
- bouncerRepository.primaryBouncerShow,
- isDozingOrAod
+ combine(
+ keyguardTransitionInteractor.get().currentKeyguardState,
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor.get().currentScene
+ } else {
+ flowOf(Scenes.Lockscreen)
+ },
+ ::Pair
+ )
+ .flatMapLatest { (currentKeyguardState, transitionState) ->
+ if (currentKeyguardState == KeyguardState.GONE) {
+ flowOf(false)
+ } else if (
+ SceneContainerFlag.isEnabled && transitionState == Scenes.Gone
) {
- fingerprintAllowed,
- keyguardDismissible,
- primaryBouncerShowing,
- dozing ->
- fingerprintAllowed &&
- !keyguardDismissible &&
- !primaryBouncerShowing &&
- !dozing
+ flowOf(false)
+ } else {
+ combine(
+ deviceEntryFingerprintAuthInteractor
+ .get()
+ .isFingerprintAuthCurrentlyAllowed,
+ keyguardInteractor.get().isKeyguardDismissible,
+ bouncerRepository.primaryBouncerShow,
+ isDozingOrAod
+ ) {
+ fingerprintAllowed,
+ keyguardDismissible,
+ primaryBouncerShowing,
+ dozing ->
+ fingerprintAllowed &&
+ !keyguardDismissible &&
+ !primaryBouncerShowing &&
+ !dozing
+ }
}
}
- }
} else {
flowOf(false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt
index 1de3459..7f137f3 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt
@@ -21,5 +21,5 @@
@Module
interface CommunalRepositoryModule {
- @Binds fun communalRepository(impl: CommunalRepositoryImpl): CommunalRepository
+ @Binds fun communalRepository(impl: CommunalSceneRepositoryImpl): CommunalSceneRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
rename to packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt
index 8bfd8d9..d6d08b4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt
@@ -36,7 +36,7 @@
import kotlinx.coroutines.flow.stateIn
/** Encapsulates the state of communal mode. */
-interface CommunalRepository {
+interface CommunalSceneRepository {
/**
* Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene].
*/
@@ -48,6 +48,9 @@
/** Updates the requested scene. */
fun changeScene(toScene: SceneKey, transitionKey: TransitionKey? = null)
+ /** Immediately snaps to the desired scene. */
+ fun snapToScene(toScene: SceneKey)
+
/**
* Updates the transition state of the hub [SceneTransitionLayout].
*
@@ -58,12 +61,12 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
-class CommunalRepositoryImpl
+class CommunalSceneRepositoryImpl
@Inject
constructor(
@Background backgroundScope: CoroutineScope,
@Communal private val sceneDataSource: SceneDataSource,
-) : CommunalRepository {
+) : CommunalSceneRepository {
override val currentScene: StateFlow<SceneKey> = sceneDataSource.currentScene
@@ -82,6 +85,10 @@
sceneDataSource.changeScene(toScene, transitionKey)
}
+ override fun snapToScene(toScene: SceneKey) {
+ sceneDataSource.snapToScene(toScene)
+ }
+
/**
* Updates the transition state of the hub [SceneTransitionLayout].
*
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 9599a88..2be28ca 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -29,7 +29,6 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.data.repository.CommunalMediaRepository
import com.android.systemui.communal.data.repository.CommunalPrefsRepository
-import com.android.systemui.communal.data.repository.CommunalRepository
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent
@@ -97,7 +96,6 @@
@Application val applicationScope: CoroutineScope,
@Background val bgDispatcher: CoroutineDispatcher,
broadcastDispatcher: BroadcastDispatcher,
- private val communalRepository: CommunalRepository,
private val widgetRepository: CommunalWidgetRepository,
private val communalPrefsRepository: CommunalPrefsRepository,
mediaRepository: CommunalMediaRepository,
@@ -110,6 +108,7 @@
private val userTracker: UserTracker,
private val activityStarter: ActivityStarter,
private val userManager: UserManager,
+ private val communalSceneInteractor: CommunalSceneInteractor,
sceneInteractor: SceneInteractor,
@CommunalLog logBuffer: LogBuffer,
@CommunalTableLog tableLogBuffer: TableLogBuffer,
@@ -174,15 +173,19 @@
*
* If [isCommunalAvailable] is false, will return [CommunalScenes.Blank]
*/
- val desiredScene: Flow<SceneKey> =
- communalRepository.currentScene.combine(isCommunalAvailable) { scene, available ->
- if (available) scene else CommunalScenes.Blank
- }
+ @Deprecated(
+ "Use com.android.systemui.communal.domain.interactor.CommunalSceneInteractor instead"
+ )
+ val desiredScene: Flow<SceneKey> = communalSceneInteractor.currentScene
/** Transition state of the hub mode. */
- val transitionState: StateFlow<ObservableTransitionState> = communalRepository.transitionState
+ @Deprecated(
+ "Use com.android.systemui.communal.domain.interactor.CommunalSceneInteractor instead"
+ )
+ val transitionState: StateFlow<ObservableTransitionState> =
+ communalSceneInteractor.transitionState
- val _userActivity: MutableSharedFlow<Unit> =
+ private val _userActivity: MutableSharedFlow<Unit> =
MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
val userActivity: Flow<Unit> = _userActivity.asSharedFlow()
@@ -212,32 +215,18 @@
*
* Note that you must call is with `null` when the UI is done or risk a memory leak.
*/
- fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
- communalRepository.setTransitionState(transitionState)
- }
+ @Deprecated(
+ "Use com.android.systemui.communal.domain.interactor.CommunalSceneInteractor instead"
+ )
+ fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) =
+ communalSceneInteractor.setTransitionState(transitionState)
/** Returns a flow that tracks the progress of transitions to the given scene from 0-1. */
+ @Deprecated(
+ "Use com.android.systemui.communal.domain.interactor.CommunalSceneInteractor instead"
+ )
fun transitionProgressToScene(targetScene: SceneKey) =
- transitionState
- .flatMapLatest { state ->
- when (state) {
- is ObservableTransitionState.Idle ->
- flowOf(CommunalTransitionProgress.Idle(state.currentScene))
- is ObservableTransitionState.Transition ->
- if (state.toScene == targetScene) {
- state.progress.map {
- CommunalTransitionProgress.Transition(
- // Clamp the progress values between 0 and 1 as actual progress
- // values can be higher than 0 or lower than 1 due to a fling.
- progress = it.coerceIn(0.0f, 1.0f)
- )
- }
- } else {
- flowOf(CommunalTransitionProgress.OtherTransition)
- }
- }
- }
- .distinctUntilChanged()
+ communalSceneInteractor.transitionProgressToScene(targetScene)
/**
* Flow that emits a boolean if the communal UI is the target scene, ie. the [desiredScene] is
@@ -283,34 +272,30 @@
* This will not be true while transitioning to the hub and will turn false immediately when a
* swipe to exit the hub starts.
*/
- val isIdleOnCommunal: StateFlow<Boolean> =
- communalRepository.transitionState
- .map {
- it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Communal
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = false,
- )
+ @Deprecated(
+ "Use com.android.systemui.communal.domain.interactor.CommunalSceneInteractor instead"
+ )
+ val isIdleOnCommunal: StateFlow<Boolean> = communalSceneInteractor.isIdleOnCommunal
/**
* Flow that emits a boolean if any portion of the communal UI is visible at all.
*
* This flow will be true during any transition and when idle on the communal scene.
*/
- val isCommunalVisible: Flow<Boolean> =
- communalRepository.transitionState.map {
- !(it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Blank)
- }
+ @Deprecated(
+ "Use com.android.systemui.communal.domain.interactor.CommunalSceneInteractor instead"
+ )
+ val isCommunalVisible: Flow<Boolean> = communalSceneInteractor.isCommunalVisible
/**
* Asks for an asynchronous scene witch to [newScene], which will use the corresponding
* installed transition or the one specified by [transitionKey], if provided.
*/
- fun changeScene(newScene: SceneKey, transitionKey: TransitionKey? = null) {
- communalRepository.changeScene(newScene, transitionKey)
- }
+ @Deprecated(
+ "Use com.android.systemui.communal.domain.interactor.CommunalSceneInteractor instead"
+ )
+ fun changeScene(newScene: SceneKey, transitionKey: TransitionKey? = null) =
+ communalSceneInteractor.changeScene(newScene, transitionKey)
fun setEditModeOpen(isOpen: Boolean) {
_editModeOpen.value = isOpen
@@ -579,17 +564,3 @@
}
}
}
-
-/** Simplified transition progress data class for tracking a single transition between scenes. */
-sealed class CommunalTransitionProgress {
- /** No transition/animation is currently running. */
- data class Idle(val scene: SceneKey) : CommunalTransitionProgress()
-
- /** There is a transition animating to the expected scene. */
- data class Transition(
- val progress: Float,
- ) : CommunalTransitionProgress()
-
- /** There is a transition animating to a scene other than the expected scene. */
- data object OtherTransition : CommunalTransitionProgress()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
new file mode 100644
index 0000000..5cfe979
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.domain.interactor
+
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionKey
+import com.android.systemui.communal.data.repository.CommunalSceneRepository
+import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+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.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class CommunalSceneInteractor
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val communalSceneRepository: CommunalSceneRepository,
+) {
+ /**
+ * Asks for an asynchronous scene witch to [newScene], which will use the corresponding
+ * installed transition or the one specified by [transitionKey], if provided.
+ */
+ fun changeScene(newScene: SceneKey, transitionKey: TransitionKey? = null) {
+ communalSceneRepository.changeScene(newScene, transitionKey)
+ }
+
+ /** Immediately snaps to the new scene. */
+ fun snapToScene(newScene: SceneKey) {
+ communalSceneRepository.snapToScene(newScene)
+ }
+
+ /**
+ * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene].
+ */
+ val currentScene: Flow<SceneKey> = communalSceneRepository.currentScene
+
+ /** Transition state of the hub mode. */
+ val transitionState: StateFlow<ObservableTransitionState> =
+ communalSceneRepository.transitionState
+
+ /**
+ * Updates the transition state of the hub [SceneTransitionLayout].
+ *
+ * Note that you must call is with `null` when the UI is done or risk a memory leak.
+ */
+ fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
+ communalSceneRepository.setTransitionState(transitionState)
+ }
+
+ /** Returns a flow that tracks the progress of transitions to the given scene from 0-1. */
+ fun transitionProgressToScene(targetScene: SceneKey) =
+ transitionState
+ .flatMapLatest { state ->
+ when (state) {
+ is ObservableTransitionState.Idle ->
+ flowOf(CommunalTransitionProgressModel.Idle(state.currentScene))
+ is ObservableTransitionState.Transition ->
+ if (state.toScene == targetScene) {
+ state.progress.map {
+ CommunalTransitionProgressModel.Transition(
+ // Clamp the progress values between 0 and 1 as actual progress
+ // values can be higher than 0 or lower than 1 due to a fling.
+ progress = it.coerceIn(0.0f, 1.0f)
+ )
+ }
+ } else {
+ flowOf(CommunalTransitionProgressModel.OtherTransition)
+ }
+ }
+ }
+ .distinctUntilChanged()
+
+ /**
+ * Flow that emits a boolean if the communal UI is fully visible and not in transition.
+ *
+ * This will not be true while transitioning to the hub and will turn false immediately when a
+ * swipe to exit the hub starts.
+ */
+ val isIdleOnCommunal: StateFlow<Boolean> =
+ transitionState
+ .map {
+ it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Communal
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
+
+ /**
+ * Flow that emits a boolean if any portion of the communal UI is visible at all.
+ *
+ * This flow will be true during any transition and when idle on the communal scene.
+ */
+ val isCommunalVisible: Flow<Boolean> =
+ transitionState.map {
+ !(it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Blank)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalTransitionProgressModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalTransitionProgressModel.kt
new file mode 100644
index 0000000..e3187c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalTransitionProgressModel.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.domain.model
+
+import com.android.compose.animation.scene.SceneKey
+
+/** Simplified transition progress data class for tracking a single transition between scenes. */
+sealed interface CommunalTransitionProgressModel {
+ /** No transition/animation is currently running. */
+ data class Idle(val scene: SceneKey) : CommunalTransitionProgressModel
+
+ /** There is a transition animating to the expected scene. */
+ data class Transition(
+ val progress: Float,
+ ) : CommunalTransitionProgressModel
+
+ /** There is a transition animating to a scene other than the expected scene. */
+ data object OtherTransition : CommunalTransitionProgressModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
index a3c61a4..73cfb52 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
@@ -26,4 +26,6 @@
object CommunalTransitionKeys {
/** Fades the glanceable hub without any translation */
val SimpleFade = TransitionKey("SimpleFade")
+ /** Immediately transitions without any delay */
+ val Immediately = TransitionKey("Immediately")
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
index b7e8205..058ca4d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
@@ -83,7 +83,9 @@
override fun allocateAppWidgetId(): Int {
return super.allocateAppWidgetId().also { appWidgetId ->
backgroundScope.launch {
- observers.forEach { observer -> observer.onAllocateAppWidgetId(appWidgetId) }
+ synchronized(observers) {
+ observers.forEach { observer -> observer.onAllocateAppWidgetId(appWidgetId) }
+ }
}
}
}
@@ -91,18 +93,28 @@
override fun deleteAppWidgetId(appWidgetId: Int) {
super.deleteAppWidgetId(appWidgetId)
backgroundScope.launch {
- observers.forEach { observer -> observer.onDeleteAppWidgetId(appWidgetId) }
+ synchronized(observers) {
+ observers.forEach { observer -> observer.onDeleteAppWidgetId(appWidgetId) }
+ }
}
}
override fun startListening() {
super.startListening()
- backgroundScope.launch { observers.forEach { observer -> observer.onHostStartListening() } }
+ backgroundScope.launch {
+ synchronized(observers) {
+ observers.forEach { observer -> observer.onHostStartListening() }
+ }
+ }
}
override fun stopListening() {
super.stopListening()
- backgroundScope.launch { observers.forEach { observer -> observer.onHostStopListening() } }
+ backgroundScope.launch {
+ synchronized(observers) {
+ observers.forEach { observer -> observer.onHostStopListening() }
+ }
+ }
}
fun addObserver(observer: Observer) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 93f3793..339e8f0 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -75,7 +75,8 @@
import com.android.systemui.log.dagger.LogModule;
import com.android.systemui.log.dagger.MonitorLog;
import com.android.systemui.log.table.TableLogBuffer;
-import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
+import com.android.systemui.mediaprojection.MediaProjectionModule;
+import com.android.systemui.mediaprojection.appselector.MediaProjectionActivitiesModule;
import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherModule;
import com.android.systemui.model.SceneContainerPlugin;
import com.android.systemui.model.SysUiState;
@@ -225,6 +226,7 @@
KeyguardSectionsModule.class,
LetterboxModule.class,
LogModule.class,
+ MediaProjectionActivitiesModule.class,
MediaProjectionModule.class,
MediaProjectionTaskSwitcherModule.class,
MotionToolModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
index ec574d2..a5eafa9 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
@@ -28,6 +28,7 @@
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flatMapLatest
@@ -43,9 +44,15 @@
biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
fingerprintPropertyRepository: FingerprintPropertyRepository,
) {
- /** Whether fingerprint authentication is currently running or not */
+ /**
+ * Whether fingerprint authentication is currently running or not. This does not mean the user
+ * [isEngaged] with the fingerprint.
+ */
val isRunning: Flow<Boolean> = repository.isRunning
+ /** Whether the user is actively engaging with the fingerprint sensor */
+ val isEngaged: StateFlow<Boolean> = repository.isEngaged
+
/** Provide the current status of fingerprint authentication. */
val authenticationStatus: Flow<FingerprintAuthenticationStatus> =
repository.authenticationStatus
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 9cdba58..f2a544e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -77,7 +77,9 @@
import com.android.keyguard.mediator.ScreenOnCoordinator;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor;
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
@@ -101,6 +103,7 @@
import java.util.ArrayList;
import java.util.Map;
import java.util.WeakHashMap;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -116,6 +119,7 @@
private final DisplayTracker mDisplayTracker;
private final PowerInteractor mPowerInteractor;
private final Lazy<SceneInteractor> mSceneInteractorLazy;
+ private final Executor mMainExecutor;
private static RemoteAnimationTarget[] wrap(TransitionInfo info, boolean wallpapers,
SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap,
@@ -308,6 +312,7 @@
}
private final WindowManagerOcclusionManager mWmOcclusionManager;
+ private final KeyguardEnabledInteractor mKeyguardEnabledInteractor;
private final Lazy<FoldGracePeriodProvider> mFoldGracePeriodProvider = new Lazy<>() {
@Override
@@ -331,7 +336,9 @@
FeatureFlags featureFlags,
PowerInteractor powerInteractor,
WindowManagerOcclusionManager windowManagerOcclusionManager,
- Lazy<SceneInteractor> sceneInteractorLazy) {
+ Lazy<SceneInteractor> sceneInteractorLazy,
+ @Main Executor mainExecutor,
+ KeyguardEnabledInteractor keyguardEnabledInteractor) {
super();
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
@@ -341,6 +348,7 @@
mFlags = featureFlags;
mPowerInteractor = powerInteractor;
mSceneInteractorLazy = sceneInteractorLazy;
+ mMainExecutor = mainExecutor;
if (KeyguardWmStateRefactor.isEnabled()) {
WindowManagerLockscreenVisibilityViewBinder.bind(
@@ -355,6 +363,7 @@
}
mWmOcclusionManager = windowManagerOcclusionManager;
+ mKeyguardEnabledInteractor = keyguardEnabledInteractor;
}
@Override
@@ -593,6 +602,7 @@
public void setKeyguardEnabled(boolean enabled) {
trace("setKeyguardEnabled enabled" + enabled);
checkPermission();
+ mKeyguardEnabledInteractor.notifyKeyguardEnabled(enabled);
mKeyguardViewMediator.setKeyguardEnabled(enabled);
}
@@ -619,8 +629,8 @@
mKeyguardViewMediator.showDismissibleKeyguard();
if (SceneContainerFlag.isEnabled() && mFoldGracePeriodProvider.get().isEnabled()) {
- mSceneInteractorLazy.get().changeScene(
- Scenes.Lockscreen, "KeyguardService.showDismissibleKeyguard");
+ mMainExecutor.execute(() -> mSceneInteractorLazy.get().changeScene(
+ Scenes.Lockscreen, "KeyguardService.showDismissibleKeyguard"));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index 0ebc92e..b1589da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -40,9 +40,13 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
@@ -57,6 +61,9 @@
*/
val isRunning: Flow<Boolean>
+ /** Whether the fingerprint sensor is actively authenticating. */
+ val isEngaged: StateFlow<Boolean>
+
/**
* Fingerprint sensor type present on the device, null if fingerprint sensor is not available.
*/
@@ -176,6 +183,17 @@
mainDispatcher
) // keyguardUpdateMonitor requires registration on main thread.
+ override val isEngaged: StateFlow<Boolean> =
+ authenticationStatus
+ .map { it.isEngaged }
+ .filterNotNull()
+ .map { it }
+ .stateIn(
+ scope = scope,
+ started = WhileSubscribed(),
+ initialValue = false,
+ )
+
// TODO(b/322555228) Remove after consolidating device entry auth messages with BP auth messages
// in BiometricStatusRepository
/**
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 8a53dd1..a2bbcad 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
@@ -109,6 +109,19 @@
)
val isKeyguardGoingAway: Flow<Boolean>
+ /**
+ * Whether the keyguard is enabled, per [KeyguardService]. If the keyguard is not enabled, the
+ * lockscreen cannot be shown and the device will go from AOD/DOZING directly to GONE.
+ *
+ * Keyguard can be disabled by selecting Security: "None" in settings, or by apps that hold
+ * permission to do so (such as Phone).
+ *
+ * If the keyguard is disabled while we're locked, we will transition to GONE unless we're in
+ * lockdown mode. If the keyguard is re-enabled, we'll transition back to LOCKSCREEN if we were
+ * locked when it was disabled.
+ */
+ val isKeyguardEnabled: StateFlow<Boolean>
+
/** Is the always-on display available to be used? */
val isAodAvailable: StateFlow<Boolean>
@@ -269,6 +282,9 @@
"'keyguardDoneAnimationsFinished' is when the GONE transition is finished."
)
fun keyguardDoneAnimationsFinished()
+
+ /** Sets whether the keyguard is enabled (see [isKeyguardEnabled]). */
+ fun setKeyguardEnabled(enabled: Boolean)
}
/** Encapsulates application state for the keyguard. */
@@ -439,6 +455,9 @@
awaitClose { keyguardStateController.removeCallback(callback) }
}
+ private val _isKeyguardEnabled = MutableStateFlow(true)
+ override val isKeyguardEnabled: StateFlow<Boolean> = _isKeyguardEnabled.asStateFlow()
+
private val _isDozing = MutableStateFlow(statusBarStateController.isDozing)
override val isDozing: StateFlow<Boolean> = _isDozing.asStateFlow()
@@ -664,6 +683,10 @@
_clockShouldBeCentered.value = shouldBeCentered
}
+ override fun setKeyguardEnabled(enabled: Boolean) {
+ _isKeyguardEnabled.value = enabled
+ }
+
private fun statusBarStateIntToObject(value: Int): StatusBarState {
return when (value) {
0 -> StatusBarState.SHADE
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index a306954..01109af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -22,6 +22,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion.isWakeAndUnlock
@@ -50,6 +51,7 @@
private val keyguardInteractor: KeyguardInteractor,
powerInteractor: PowerInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+ val deviceEntryRepository: DeviceEntryRepository,
) :
TransitionInteractor(
fromState = KeyguardState.AOD,
@@ -125,7 +127,12 @@
val shouldTransitionToOccluded =
!KeyguardWmStateRefactor.isEnabled && isKeyguardOccludedLegacy
- if (canDismissLockscreen) {
+ val shouldTransitionToGone =
+ (!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen) ||
+ (KeyguardWmStateRefactor.isEnabled &&
+ !deviceEntryRepository.isLockscreenEnabled())
+
+ if (shouldTransitionToGone) {
startTransitionTo(
toState = KeyguardState.GONE,
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 115fc36..7d3de30 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -22,6 +22,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion.isWakeAndUnlock
@@ -50,6 +51,7 @@
powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+ val deviceEntryRepository: DeviceEntryRepository,
) :
TransitionInteractor(
fromState = KeyguardState.DOZING,
@@ -99,7 +101,9 @@
canTransitionToGoneOnWake,
primaryBouncerShowing) ->
startTransitionTo(
- if (isWakeAndUnlock(biometricUnlockState.mode)) {
+ if (!deviceEntryRepository.isLockscreenEnabled()) {
+ KeyguardState.GONE
+ } else if (isWakeAndUnlock(biometricUnlockState.mode)) {
KeyguardState.GONE
} else if (canTransitionToGoneOnWake) {
KeyguardState.GONE
@@ -145,7 +149,12 @@
!isWakeAndUnlock(biometricUnlockState.mode)
) {
startTransitionTo(
- if (canDismissLockscreen) {
+ if (!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen) {
+ KeyguardState.GONE
+ } else if (
+ KeyguardWmStateRefactor.isEnabled &&
+ !deviceEntryRepository.isLockscreenEnabled()
+ ) {
KeyguardState.GONE
} else if (primaryBouncerShowing) {
KeyguardState.PRIMARY_BOUNCER
@@ -153,7 +162,8 @@
KeyguardState.GLANCEABLE_HUB
} else {
KeyguardState.LOCKSCREEN
- }
+ },
+ ownerReason = "waking from dozing"
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 2b3732f..8ca29c8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -25,6 +25,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
@@ -36,6 +37,7 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@SysUISingleton
@@ -52,6 +54,8 @@
private val communalInteractor: CommunalInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
private val biometricSettingsRepository: BiometricSettingsRepository,
+ private val keyguardRepository: KeyguardRepository,
+ private val keyguardEnabledInteractor: KeyguardEnabledInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.GONE,
@@ -93,6 +97,21 @@
startTransitionTo(to, ownerReason = "User initiated lockdown")
}
}
+
+ scope.launch {
+ keyguardRepository.isKeyguardEnabled
+ .filterRelevantKeyguardStateAnd { enabled -> enabled }
+ .sample(keyguardEnabledInteractor.showKeyguardWhenReenabled)
+ .filter { reshow -> reshow }
+ .collect {
+ startTransitionTo(
+ KeyguardState.LOCKSCREEN,
+ ownerReason =
+ "Keyguard was re-enabled, and we weren't GONE when it " +
+ "was originally disabled"
+ )
+ }
+ }
} else {
scope.launch("$TAG#listenForGoneToLockscreenOrHub") {
keyguardInteractor.isKeyguardShowing
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
index fcf67d5..af1ce2b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
@@ -19,7 +19,7 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.CommunalTransitionProgress
+import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -71,7 +71,7 @@
if (id == null) {
// No transition started.
if (
- transitionProgress is CommunalTransitionProgress.Transition &&
+ transitionProgress is CommunalTransitionProgressModel.Transition &&
lastStartedState == fromState
) {
transitionId =
@@ -93,7 +93,7 @@
val nextState: TransitionState
val progressFraction: Float
when (transitionProgress) {
- is CommunalTransitionProgress.Idle -> {
+ is CommunalTransitionProgressModel.Idle -> {
if (transitionProgress.scene == toScene) {
nextState = TransitionState.FINISHED
progressFraction = 1f
@@ -102,11 +102,11 @@
progressFraction = 0f
}
}
- is CommunalTransitionProgress.Transition -> {
+ is CommunalTransitionProgressModel.Transition -> {
nextState = TransitionState.RUNNING
progressFraction = transitionProgress.progress
}
- is CommunalTransitionProgress.OtherTransition -> {
+ is CommunalTransitionProgressModel.OtherTransition -> {
// Shouldn't happen but if another transition starts during the
// current one, mark the current one as canceled.
nextState = TransitionState.CANCELED
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
new file mode 100644
index 0000000..8dede01
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Logic around the keyguard being enabled/disabled, per [KeyguardService]. If the keyguard is not
+ * enabled, the lockscreen cannot be shown and the device will go from AOD/DOZING directly to GONE.
+ *
+ * Keyguard can be disabled by selecting Security: "None" in settings, or by apps that hold
+ * permission to do so (such as Phone). Some CTS tests also disable keyguard in onCreate or onStart
+ * rather than simply dismissing the keyguard or setting up the device to have Security: None, for
+ * reasons unknown.
+ */
+@SysUISingleton
+class KeyguardEnabledInteractor
+@Inject
+constructor(
+ @Application scope: CoroutineScope,
+ val repository: KeyguardRepository,
+ val biometricSettingsRepository: BiometricSettingsRepository,
+ transitionInteractor: KeyguardTransitionInteractor,
+) {
+
+ init {
+ /**
+ * Whenever keyguard is disabled, transition to GONE unless we're in lockdown or already
+ * GONE.
+ */
+ scope.launch {
+ repository.isKeyguardEnabled
+ .filter { enabled -> !enabled }
+ .sampleCombine(
+ biometricSettingsRepository.isCurrentUserInLockdown,
+ transitionInteractor.currentTransitionInfoInternal,
+ )
+ .collect { (_, inLockdown, currentTransitionInfo) ->
+ if (currentTransitionInfo.to != KeyguardState.GONE && !inLockdown) {
+ transitionInteractor.startDismissKeyguardTransition("keyguard disabled")
+ }
+ }
+ }
+ }
+
+ /**
+ * Whether we need to show the keyguard when the keyguard is re-enabled, since we hid it when it
+ * became disabled.
+ */
+ val showKeyguardWhenReenabled: Flow<Boolean> =
+ repository.isKeyguardEnabled
+ // Whenever the keyguard is disabled...
+ .filter { enabled -> !enabled }
+ .sampleCombine(
+ transitionInteractor.currentTransitionInfoInternal,
+ biometricSettingsRepository.isCurrentUserInLockdown
+ )
+ .map { (_, transitionInfo, inLockdown) ->
+ // ...we hide the keyguard, if it's showing and we're not in lockdown. In that case,
+ // we want to remember that and re-show it when keyguard is enabled again.
+ transitionInfo.to != KeyguardState.GONE && !inLockdown
+ }
+
+ fun notifyKeyguardEnabled(enabled: Boolean) {
+ repository.setKeyguardEnabled(enabled)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index 8ba09bd..fb65a6d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -176,7 +176,8 @@
if (!returningToGoneAfterCancellation) {
// By default, apply the lockscreen visibility of the current state.
- KeyguardState.lockscreenVisibleInState(currentState)
+ deviceEntryInteractor.get().isLockscreenEnabled() &&
+ KeyguardState.lockscreenVisibleInState(currentState)
} else {
// If we're transitioning to GONE after a prior canceled transition from
// GONE, then this is the camera launch transition from an asleep state back
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
index d8b7b4a..e92dec0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
@@ -19,6 +19,7 @@
import android.hardware.biometrics.BiometricFingerprintConstants
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_UNKNOWN
import android.hardware.fingerprint.FingerprintManager
import android.os.SystemClock.elapsedRealtime
import com.android.systemui.biometrics.shared.model.AuthenticationReason
@@ -26,26 +27,43 @@
/**
* Fingerprint authentication status provided by
* [com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository]
+ *
+ * @isEngaged whether fingerprint is actively engaged by the user. This is distinct from fingerprint
+ * running on the device. Can be null if the status does not have an associated isEngaged state.
*/
-sealed class FingerprintAuthenticationStatus
+sealed class FingerprintAuthenticationStatus(val isEngaged: Boolean?)
/** Fingerprint authentication success status. */
data class SuccessFingerprintAuthenticationStatus(
val userId: Int,
val isStrongBiometric: Boolean,
-) : FingerprintAuthenticationStatus()
+) : FingerprintAuthenticationStatus(isEngaged = false)
/** Fingerprint authentication help message. */
data class HelpFingerprintAuthenticationStatus(
val msgId: Int,
val msg: String?,
-) : FingerprintAuthenticationStatus()
+) : FingerprintAuthenticationStatus(isEngaged = null)
/** Fingerprint acquired message. */
data class AcquiredFingerprintAuthenticationStatus(
val authenticationReason: AuthenticationReason,
val acquiredInfo: Int
-) : FingerprintAuthenticationStatus() {
+) :
+ FingerprintAuthenticationStatus(
+ isEngaged =
+ if (acquiredInfo == FINGERPRINT_ACQUIRED_START) {
+ true
+ } else if (
+ acquiredInfo == FINGERPRINT_ACQUIRED_UNKNOWN ||
+ acquiredInfo == FINGERPRINT_ACQUIRED_GOOD
+ ) {
+ null
+ } else {
+ // soft errors that indicate fingerprint activity ended
+ false
+ }
+ ) {
val fingerprintCaptureStarted: Boolean = acquiredInfo == FINGERPRINT_ACQUIRED_START
@@ -53,7 +71,8 @@
}
/** Fingerprint authentication failed message. */
-data object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus()
+data object FailFingerprintAuthenticationStatus :
+ FingerprintAuthenticationStatus(isEngaged = false)
/** Fingerprint authentication error message */
data class ErrorFingerprintAuthenticationStatus(
@@ -61,7 +80,7 @@
val msg: String? = null,
// present to break equality check if the same error occurs repeatedly.
val createdAt: Long = elapsedRealtime(),
-) : FingerprintAuthenticationStatus() {
+) : FingerprintAuthenticationStatus(isEngaged = false) {
fun isCancellationError(): Boolean =
msgId == BiometricFingerprintConstants.FINGERPRINT_ERROR_CANCELED ||
msgId == BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
index 200d30c..7a2e610 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
@@ -88,6 +88,12 @@
}
}
+ /**
+ * Setups different icon states.
+ * - All lottie views will require a LottieOnCompositionLoadedListener to update
+ * LottieProperties (like color) of the view.
+ * - Drawable properties can be updated using ImageView properties like imageTintList.
+ */
private fun setupIconStates() {
// Lockscreen States
// LOCK
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index 0aa6d12..0f1f5c1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -64,13 +64,6 @@
}
}
- private val color: Flow<Int> =
- deviceEntryIconViewModel.useBackgroundProtection.flatMapLatest { useBgProtection ->
- configurationInteractor.onAnyConfigurationChange
- .map { getColor(useBgProtection) }
- .onStart { emit(getColor(useBgProtection)) }
- }
-
// While dozing, the display can show the AOD UI; show the AOD udfps when dozing
private val useAodIconVariant: Flow<Boolean> =
deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfspEnrolled ->
@@ -81,6 +74,22 @@
}
}
+ private val color: Flow<Int> =
+ useAodIconVariant
+ .flatMapLatest { useAodVariant ->
+ if (useAodVariant) {
+ flowOf(android.graphics.Color.WHITE)
+ } else {
+ deviceEntryIconViewModel.useBackgroundProtection.flatMapLatest { useBgProtection
+ ->
+ configurationInteractor.onAnyConfigurationChange
+ .map { getColor(useBgProtection) }
+ .onStart { emit(getColor(useBgProtection)) }
+ }
+ }
+ }
+ .distinctUntilChanged()
+
private val padding: Flow<Int> =
deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { udfpsSupported ->
if (udfpsSupported) {
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 5babc8b..14890d7 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -458,7 +458,7 @@
@SysUISingleton
@CarrierTextManagerLog
public static LogBuffer provideCarrierTextManagerLog(LogBufferFactory factory) {
- return factory.create("CarrierTextManagerLog", 100);
+ return factory.create("CarrierTextManagerLog", 400);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionCaptureTarget.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionCaptureTarget.kt
index a618490..de56c84 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionCaptureTarget.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionCaptureTarget.kt
@@ -21,15 +21,17 @@
import android.os.Parcelable
/**
- * Class that represents an area that should be captured. Currently it has only a launch cookie that
- * represents a task but we potentially could add more identifiers e.g. for a pair of tasks.
+ * Class that represents an area that should be captured. Currently it has only a launch cookie and
+ * id that represents a task but we potentially could add more identifiers e.g. for a pair of tasks.
*/
-data class MediaProjectionCaptureTarget(val launchCookie: LaunchCookie?) : Parcelable {
+data class MediaProjectionCaptureTarget(val launchCookie: LaunchCookie?, val taskId: Int) :
+ Parcelable {
- constructor(parcel: Parcel) : this(LaunchCookie.readFromParcel(parcel))
+ constructor(parcel: Parcel) : this(LaunchCookie.readFromParcel(parcel), parcel.readInt())
override fun writeToParcel(dest: Parcel, flags: Int) {
LaunchCookie.writeToParcel(launchCookie, dest)
+ dest.writeInt(taskId)
}
override fun describeContents(): Int = 0
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionModule.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionModule.kt
new file mode 100644
index 0000000..3489459
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionModule.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.mediaprojection
+
+import com.android.systemui.mediaprojection.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.data.repository.MediaProjectionRepository
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface MediaProjectionModule {
+ @Binds fun mediaRepository(impl: MediaProjectionManagerRepository): MediaProjectionRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
index 4685c5a..d6affd2 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
@@ -174,7 +174,7 @@
// is created and ready to be captured.
val activityStarted =
activityLauncher.startActivityAsUser(intent, userHandle, activityOptions.toBundle()) {
- returnSelectedApp(launchCookie)
+ returnSelectedApp(launchCookie, taskId = -1)
}
// Rely on the ActivityManager to pop up a dialog regarding app suspension
@@ -232,7 +232,7 @@
}
}
- override fun returnSelectedApp(launchCookie: LaunchCookie) {
+ override fun returnSelectedApp(launchCookie: LaunchCookie, taskId: Int) {
taskSelected = true
if (intent.hasExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER)) {
// The client requested to return the result in the result receiver instead of
@@ -242,7 +242,7 @@
EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
ResultReceiver::class.java
) as ResultReceiver
- val captureRegion = MediaProjectionCaptureTarget(launchCookie)
+ val captureRegion = MediaProjectionCaptureTarget(launchCookie, taskId)
val data = Bundle().apply { putParcelable(KEY_CAPTURE_TARGET, captureRegion) }
resultReceiver.send(RESULT_OK, data)
// TODO(b/279175710): Ensure consent result is always set here. Skipping this for now
@@ -255,6 +255,7 @@
val projection = IMediaProjection.Stub.asInterface(mediaProjectionBinder)
projection.setLaunchCookie(launchCookie)
+ projection.setTaskId(taskId)
val intent = Intent()
intent.putExtra(EXTRA_MEDIA_PROJECTION, projection.asBinder())
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index f08bc17..9b1ca1e 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -65,7 +65,7 @@
subcomponents = [MediaProjectionAppSelectorComponent::class],
includes = [MediaProjectionDevicePolicyModule::class]
)
-interface MediaProjectionModule {
+interface MediaProjectionActivitiesModule {
@Binds
@IntoMap
@ClassKey(MediaProjectionAppSelectorActivity::class)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt
index f204b3e..6857000 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt
@@ -9,7 +9,10 @@
interface MediaProjectionAppSelectorResultHandler {
/**
* Return selected app to the original caller of the media projection app picker.
- * @param launchCookie launch cookie of the launched activity of the target app
+ * @param launchCookie launch cookie of the launched activity of the target app, always set
+ * regardless of launching a new task or a recent task
+ * @param taskId id of the launched task of the target app, only set to a positive int when
+ * launching a recent task, otherwise set to -1 by default
*/
- fun returnSelectedApp(launchCookie: LaunchCookie)
+ fun returnSelectedApp(launchCookie: LaunchCookie, taskId: Int)
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index 9549ab1..46aa064 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -144,10 +144,9 @@
activityOptions.launchDisplayId = task.displayId
activityOptions.setLaunchCookie(launchCookie)
- val handleResult: () -> Unit = { resultHandler.returnSelectedApp(launchCookie)}
-
val taskId = task.taskId
val splitBounds = task.splitBounds
+ val handleResult: () -> Unit = { resultHandler.returnSelectedApp(launchCookie, taskId)}
if (pssAppSelectorRecentsSplitScreen() &&
task.isLaunchingInSplitScreen() &&
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/model/MediaProjectionState.kt
similarity index 87%
rename from packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/data/model/MediaProjectionState.kt
index cfbcaf9..1d5f6f5 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/model/MediaProjectionState.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.mediaprojection.taskswitcher.data.model
+package com.android.systemui.mediaprojection.data.model
import android.app.ActivityManager.RunningTaskInfo
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
similarity index 88%
rename from packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
index 74d1992..3ce0a1e0 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.mediaprojection.taskswitcher.data.repository
+package com.android.systemui.mediaprojection.data.repository
import android.app.ActivityManager.RunningTaskInfo
import android.media.projection.MediaProjectionInfo
@@ -24,20 +24,21 @@
import android.view.ContentRecordingSession
import android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
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.mediaprojection.MediaProjectionServiceHelper
-import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import com.android.systemui.mediaprojection.data.model.MediaProjectionState
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -88,7 +89,11 @@
mediaProjectionManager.addCallback(callback, handler)
awaitClose { mediaProjectionManager.removeCallback(callback) }
}
- .shareIn(scope = applicationScope, started = SharingStarted.Lazily, replay = 1)
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Lazily,
+ initialValue = MediaProjectionState.NotProjecting,
+ )
private suspend fun stateForSession(session: ContentRecordingSession?): MediaProjectionState {
if (session == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt
index e495466..21300db 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.mediaprojection.taskswitcher.data.repository
+package com.android.systemui.mediaprojection.data.repository
import android.app.ActivityManager.RunningTaskInfo
-import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import kotlinx.coroutines.flow.Flow
/** Represents a repository to retrieve and change data related to media projection. */
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt
index 22ad07e..eb38958 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt
@@ -17,16 +17,11 @@
package com.android.systemui.mediaprojection.taskswitcher
import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository
import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository
import dagger.Binds
import dagger.Module
@Module
interface MediaProjectionTaskSwitcherModule {
-
- @Binds fun mediaRepository(impl: MediaProjectionManagerRepository): MediaProjectionRepository
-
@Binds fun tasksRepository(impl: ActivityTaskManagerTasksRepository): TasksRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt
index eb9e6a5..c232d4d 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt
@@ -21,8 +21,8 @@
import android.content.Intent
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository
+import com.android.systemui.mediaprojection.data.model.MediaProjectionState
+import com.android.systemui.mediaprojection.data.repository.MediaProjectionRepository
import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository
import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
import javax.inject.Inject
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 9487085..d0f8412 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -15,6 +15,7 @@
*/
package com.android.systemui.navigationbar.gestural;
+import static android.content.pm.ActivityInfo.CONFIG_FONT_SCALE;
import static android.view.InputDevice.SOURCE_MOUSE;
import static android.view.InputDevice.SOURCE_TOUCHPAD;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
@@ -24,10 +25,13 @@
import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll;
import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
+import static java.util.stream.Collectors.joining;
+
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
@@ -47,6 +51,7 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.provider.DeviceConfig;
+import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
@@ -102,6 +107,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
@@ -255,7 +261,7 @@
private boolean mIsAttached;
private boolean mIsGestureHandlingEnabled;
- private boolean mIsTrackpadConnected;
+ private final Set<Integer> mTrackpadsConnected = new ArraySet<>();
private boolean mInGestureNavMode;
private boolean mUsingThreeButtonNav;
private boolean mIsEnabled;
@@ -358,16 +364,14 @@
private final InputManager.InputDeviceListener mInputDeviceListener =
new InputManager.InputDeviceListener() {
-
- // Only one trackpad can be connected to a device at a time, since it takes over the
- // only USB port.
- private int mTrackpadDeviceId;
-
@Override
public void onInputDeviceAdded(int deviceId) {
if (isTrackpadDevice(deviceId)) {
- mTrackpadDeviceId = deviceId;
- update(true /* isTrackpadConnected */);
+ boolean wasEmpty = mTrackpadsConnected.isEmpty();
+ mTrackpadsConnected.add(deviceId);
+ if (wasEmpty) {
+ update();
+ }
}
}
@@ -376,18 +380,29 @@
@Override
public void onInputDeviceRemoved(int deviceId) {
- if (mTrackpadDeviceId == deviceId) {
- update(false /* isTrackpadConnected */);
+ mTrackpadsConnected.remove(deviceId);
+ if (mTrackpadsConnected.isEmpty()) {
+ update();
}
}
- private void update(boolean isTrackpadConnected) {
- boolean isPreviouslyTrackpadConnected = mIsTrackpadConnected;
- mIsTrackpadConnected = isTrackpadConnected;
- if (isPreviouslyTrackpadConnected != mIsTrackpadConnected) {
- updateIsEnabled();
- updateCurrentUserResources();
+ private void update() {
+ if (mIsEnabled && !mTrackpadsConnected.isEmpty()) {
+ // Don't reinitialize gesture handling due to trackpad connecting when it's
+ // already set up.
+ return;
}
+ updateIsEnabled();
+ updateCurrentUserResources();
+ }
+
+ private boolean isTrackpadDevice(int deviceId) {
+ InputDevice inputDevice = mInputManager.getInputDevice(deviceId);
+ if (inputDevice == null) {
+ return false;
+ }
+ return inputDevice.getSources() == (InputDevice.SOURCE_MOUSE
+ | InputDevice.SOURCE_TOUCHPAD);
}
};
@@ -566,6 +581,7 @@
mOverviewProxyService.removeCallback(mQuickSwitchListener);
mSysUiState.removeCallback(mSysUiStateCallback);
mInputManager.unregisterInputDeviceListener(mInputDeviceListener);
+ mTrackpadsConnected.clear();
updateIsEnabled();
mUserTracker.removeCallback(mUserChangedCallback);
}
@@ -605,7 +621,7 @@
Trace.beginSection("EdgeBackGestureHandler#updateIsEnabled");
mIsGestureHandlingEnabled = mInGestureNavMode || (mUsingThreeButtonNav
- && mIsTrackpadConnected);
+ && !mTrackpadsConnected.isEmpty());
boolean isEnabled = mIsAttached && mIsGestureHandlingEnabled;
if (isEnabled == mIsEnabled) {
return;
@@ -867,15 +883,6 @@
mDisplaySize.y - insets.bottom);
}
- private boolean isTrackpadDevice(int deviceId) {
- InputDevice inputDevice = mInputManager.getInputDevice(deviceId);
- if (inputDevice == null) {
- return false;
- }
- return inputDevice.getSources() == (InputDevice.SOURCE_MOUSE
- | InputDevice.SOURCE_TOUCHPAD);
- }
-
private boolean desktopExcludeRegionContains(int x, int y) {
return mDesktopModeExcludeRegion.contains(x, y);
}
@@ -1175,6 +1182,10 @@
// TODO(b/332635834): Disable this logging once b/332635834 is fixed.
Log.i(DEBUG_MISSING_GESTURE_TAG, "Config changed: newConfig=" + newConfig
+ " lastReportedConfig=" + mLastReportedConfig);
+ final int diff = newConfig.diff(mLastReportedConfig);
+ if ((diff & CONFIG_FONT_SCALE) != 0 || (diff & ActivityInfo.CONFIG_DENSITY) != 0) {
+ updateCurrentUserResources();
+ }
mLastReportedConfig.updateFrom(newConfig);
updateDisplaySize();
}
@@ -1251,7 +1262,8 @@
pw.println(" mPredictionLog=" + String.join("\n", mPredictionLog));
pw.println(" mGestureLogInsideInsets=" + String.join("\n", mGestureLogInsideInsets));
pw.println(" mGestureLogOutsideInsets=" + String.join("\n", mGestureLogOutsideInsets));
- pw.println(" mIsTrackpadConnected=" + mIsTrackpadConnected);
+ pw.println(" mTrackpadsConnected=" + mTrackpadsConnected.stream().map(
+ String::valueOf).collect(joining()));
pw.println(" mUsingThreeButtonNav=" + mUsingThreeButtonNav);
pw.println(" mEdgeBackPlugin=" + mEdgeBackPlugin);
if (mEdgeBackPlugin != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index b34b370..e77bd03 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -485,6 +485,11 @@
}
@Override
+ public int getMinRows() {
+ return mMinRows;
+ }
+
+ @Override
public boolean setMaxColumns(int maxColumns) {
mMaxColumns = maxColumns;
boolean changed = false;
@@ -497,6 +502,11 @@
return changed;
}
+ @Override
+ public int getMaxColumns() {
+ return mMaxColumns;
+ }
+
/**
* Set the amount of excess space that we gave this view compared to the actual available
* height. This is because this view is in a scrollview.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 00757b7..9c8c17b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -42,6 +42,7 @@
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -441,7 +442,7 @@
}
private boolean needsDynamicRowsAndColumns() {
- return true;
+ return !SceneContainerFlag.isEnabled();
}
private void switchAllContentToParent(ViewGroup parent, QSTileLayout newLayout) {
@@ -634,8 +635,7 @@
switchAllContentToParent(newParent, mTileLayout);
reAttachMediaHost(mediaHostView, horizontal);
if (needsDynamicRowsAndColumns()) {
- mTileLayout.setMinRows(horizontal ? 2 : 1);
- mTileLayout.setMaxColumns(horizontal ? 2 : 4);
+ setColumnRowLayout(horizontal);
}
updateMargins(mediaHostView);
if (mHorizontalLinearLayout != null) {
@@ -644,6 +644,11 @@
}
}
+ void setColumnRowLayout(boolean withMedia) {
+ mTileLayout.setMinRows(withMedia ? 2 : 1);
+ mTileLayout.setMaxColumns(withMedia ? 2 : 4);
+ }
+
private void updateMargins(ViewGroup mediaHostView) {
updateMediaHostContentMargins(mediaHostView);
updateHorizontalLinearLayoutMargins();
@@ -736,6 +741,8 @@
return false;
}
+ int getMinRows();
+
/**
* Sets the max number of columns to show
*
@@ -747,6 +754,8 @@
return false;
}
+ int getMaxColumns();
+
/**
* Sets the expansion value and proposedTranslation to panel.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index e24caf1..f76183e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -30,6 +30,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.haptics.qs.QSLongPressEffect;
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor;
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
import com.android.systemui.media.controls.ui.view.MediaHost;
import com.android.systemui.media.controls.ui.view.MediaHostState;
@@ -46,10 +47,13 @@
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.tuner.TunerService;
+import kotlinx.coroutines.flow.StateFlow;
+
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
+
/**
* Controller for {@link QSPanel}.
*/
@@ -72,6 +76,8 @@
private final BrightnessSliderController.Factory mBrightnessSliderControllerFactory;
private final BrightnessController.Factory mBrightnessControllerFactory;
+ protected final MediaCarouselInteractor mMediaCarouselInteractor;
+
private View.OnTouchListener mTileLayoutTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
@@ -94,7 +100,8 @@
FalsingManager falsingManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
SplitShadeStateController splitShadeStateController,
- Provider<QSLongPressEffect> longPRessEffectProvider) {
+ Provider<QSLongPressEffect> longPRessEffectProvider,
+ MediaCarouselInteractor mediaCarouselInteractor) {
super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost,
metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController,
longPRessEffectProvider);
@@ -113,6 +120,7 @@
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mLastDensity = view.getResources().getConfiguration().densityDpi;
mSceneContainerEnabled = SceneContainerFlag.isEnabled();
+ mMediaCarouselInteractor = mediaCarouselInteractor;
}
@Override
@@ -126,6 +134,11 @@
}
@Override
+ StateFlow<Boolean> getMediaVisibleFlow() {
+ return mMediaCarouselInteractor.getHasAnyMediaOrRecommendation();
+ }
+
+ @Override
protected void onViewAttached() {
super.onViewAttached();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 583cfb9..3b5cc61 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -41,13 +41,17 @@
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileViewImpl;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.animation.DisappearParameters;
+import com.android.systemui.util.kotlin.JavaAdapterKt;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
+import kotlinx.coroutines.flow.StateFlow;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
@@ -58,6 +62,7 @@
import javax.inject.Provider;
+
/**
* Controller for QSPanel views.
*
@@ -93,6 +98,15 @@
private final Provider<QSLongPressEffect> mLongPressEffectProvider;
+ private boolean mDestroyed = false;
+
+ private boolean mMediaVisibleFromInteractor;
+
+ private final Consumer<Boolean> mMediaOrRecommendationVisibleConsumer = mediaVisible -> {
+ mMediaVisibleFromInteractor = mediaVisible;
+ setLayoutForMediaInScene();
+ };
+
@VisibleForTesting
protected final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
new QSPanel.OnConfigurationChangedListener() {
@@ -115,7 +129,11 @@
/* newScreenLayout= */ mLastScreenLayout,
/* containerName= */ mView.getDumpableTag());
- switchTileLayoutIfNeeded();
+ if (SceneContainerFlag.isEnabled()) {
+ setLayoutForMediaInScene();
+ } else {
+ switchTileLayoutIfNeeded();
+ }
onConfigurationChanged();
if (previousSplitShadeState != mShouldUseSplitNotificationShade) {
onSplitShadeChanged(mShouldUseSplitNotificationShade);
@@ -173,6 +191,9 @@
mView.initialize(mQSLogger, mUsingMediaPlayer);
mQSLogger.logAllTilesChangeListening(mView.isListening(), mView.getDumpableTag(), "");
mHost.addCallback(mQSHostCallback);
+ if (SceneContainerFlag.isEnabled()) {
+ registerForMediaInteractorChanges();
+ }
}
/**
@@ -192,7 +213,7 @@
// will remove the attach listener. We don't need to do that, because once this object is
// detached from the graph, it will be gc.
mHost.removeCallback(mQSHostCallback);
-
+ mDestroyed = true;
for (TileRecord record : mRecords) {
record.tile.removeCallback(record.callback);
mView.removeTile(record);
@@ -207,17 +228,32 @@
mQsTileRevealController.setExpansion(mRevealExpansion);
}
- mMediaHost.addVisibilityChangeListener(mMediaHostVisibilityListener);
+ if (!SceneContainerFlag.isEnabled()) {
+ mMediaHost.addVisibilityChangeListener(mMediaHostVisibilityListener);
+ }
mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
setTiles();
mLastOrientation = getResources().getConfiguration().orientation;
mLastScreenLayout = getResources().getConfiguration().screenLayout;
mQSLogger.logOnViewAttached(mLastOrientation, mView.getDumpableTag());
+ if (SceneContainerFlag.isEnabled()) {
+ setLayoutForMediaInScene();
+ }
switchTileLayout(true);
mDumpManager.registerDumpable(mView.getDumpableTag(), this);
}
+ private void registerForMediaInteractorChanges() {
+ JavaAdapterKt.collectFlow(
+ mView,
+ getMediaVisibleFlow(),
+ mMediaOrRecommendationVisibleConsumer
+ );
+ }
+
+ abstract StateFlow<Boolean> getMediaVisibleFlow();
+
@Override
protected void onViewDetached() {
mQSLogger.logOnViewDetached(mLastOrientation, mView.getDumpableTag());
@@ -242,6 +278,7 @@
/** */
public void setTiles(Collection<QSTile> tiles, boolean collapsedView) {
+ if (mDestroyed) return;
// TODO(b/168904199): move this logic into QSPanelController.
if (!collapsedView && mQsTileRevealController != null) {
mQsTileRevealController.updateRevealedTiles(tiles);
@@ -433,6 +470,11 @@
return false;
}
+ void setLayoutForMediaInScene() {
+ boolean withMedia = shouldUseHorizontalInScene();
+ mView.setColumnRowLayout(withMedia);
+ }
+
/**
* Update the way the media disappears based on if we're using the horizontal layout
*/
@@ -473,6 +515,16 @@
== Configuration.SCREENLAYOUT_LONG_YES;
}
+ boolean shouldUseHorizontalInScene() {
+ if (mShouldUseSplitNotificationShade) {
+ return false;
+ }
+ return mMediaVisibleFromInteractor
+ && mLastOrientation == Configuration.ORIENTATION_LANDSCAPE
+ && (mLastScreenLayout & Configuration.SCREENLAYOUT_LONG_MASK)
+ == Configuration.SCREENLAYOUT_LONG_YES;
+ }
+
private void logTiles() {
for (int i = 0; i < mRecords.size(); i++) {
QSTile tile = mRecords.get(i).tile;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index 6cda740..f207b1d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -26,6 +26,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.haptics.qs.QSLongPressEffect;
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor;
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
import com.android.systemui.media.controls.ui.view.MediaHost;
import com.android.systemui.plugins.qs.QSTile;
@@ -36,6 +37,8 @@
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.util.leak.RotationUtils;
+import kotlinx.coroutines.flow.StateFlow;
+
import java.util.ArrayList;
import java.util.List;
@@ -43,12 +46,15 @@
import javax.inject.Named;
import javax.inject.Provider;
+
/** Controller for {@link QuickQSPanel}. */
@QSScope
public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> {
private final Provider<Boolean> mUsingCollapsedLandscapeMediaProvider;
+ private final MediaCarouselInteractor mMediaCarouselInteractor;
+
@Inject
QuickQSPanelController(QuickQSPanel view, QSHost qsHost,
QSCustomizerController qsCustomizerController,
@@ -58,12 +64,14 @@
Provider<Boolean> usingCollapsedLandscapeMediaProvider,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
DumpManager dumpManager, SplitShadeStateController splitShadeStateController,
- Provider<QSLongPressEffect> longPressEffectProvider
+ Provider<QSLongPressEffect> longPressEffectProvider,
+ MediaCarouselInteractor mediaCarouselInteractor
) {
super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
uiEventLogger, qsLogger, dumpManager, splitShadeStateController,
longPressEffectProvider);
mUsingCollapsedLandscapeMediaProvider = usingCollapsedLandscapeMediaProvider;
+ mMediaCarouselInteractor = mediaCarouselInteractor;
}
@Override
@@ -74,6 +82,11 @@
mMediaHost.init(MediaHierarchyManager.LOCATION_QQS);
}
+ @Override
+ StateFlow<Boolean> getMediaVisibleFlow() {
+ return mMediaCarouselInteractor.getHasActiveMediaOrRecommendation();
+ }
+
private void updateMediaExpansion() {
int rotation = getRotation();
boolean isLandscape = rotation == RotationUtils.ROTATION_LANDSCAPE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index dcb9288..ef44e5f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -12,6 +12,7 @@
import android.widget.TextView;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.FontSizeUtils;
@@ -97,12 +98,24 @@
return false;
}
+ @VisibleForTesting
+ @Override
+ public int getMinRows() {
+ return mMinRows;
+ }
+
@Override
public boolean setMaxColumns(int maxColumns) {
mMaxColumns = maxColumns;
return updateColumns();
}
+ @VisibleForTesting
+ @Override
+ public int getMaxColumns() {
+ return mMaxColumns;
+ }
+
public void addTile(TileRecord tile) {
mRecords.add(tile);
tile.tile.setListening(this, mListening);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index d161c6b..7b67993 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -28,6 +28,7 @@
import com.android.systemui.qs.panels.domain.interactor.NoopGridConsistencyInteractor
import com.android.systemui.qs.panels.shared.model.GridConsistencyLog
import com.android.systemui.qs.panels.shared.model.GridLayoutType
+import com.android.systemui.qs.panels.shared.model.IconLabelVisibilityLog
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType
import com.android.systemui.qs.panels.shared.model.StretchedGridLayoutType
@@ -35,6 +36,12 @@
import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout
import com.android.systemui.qs.panels.ui.compose.StretchedGridLayout
+import com.android.systemui.qs.panels.ui.viewmodel.IconLabelVisibilityViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.IconLabelVisibilityViewModelImpl
+import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModelImpl
+import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridSizeViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridSizeViewModelImpl
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -53,6 +60,15 @@
impl: NoopGridConsistencyInteractor
): GridTypeConsistencyInteractor
+ @Binds fun bindIconTilesViewModel(impl: IconTilesViewModelImpl): IconTilesViewModel
+
+ @Binds fun bindGridSizeViewModel(impl: InfiniteGridSizeViewModelImpl): InfiniteGridSizeViewModel
+
+ @Binds
+ fun bindIconLabelVisibilityViewModel(
+ impl: IconLabelVisibilityViewModelImpl
+ ): IconLabelVisibilityViewModel
+
@Binds @Named("Default") fun bindDefaultGridLayout(impl: PartitionedGridLayout): GridLayout
companion object {
@@ -64,6 +80,13 @@
}
@Provides
+ @SysUISingleton
+ @IconLabelVisibilityLog
+ fun providesIconTileLabelVisibilityLog(factory: LogBufferFactory): LogBuffer {
+ return factory.create("IconLabelVisibilityLog", 50)
+ }
+
+ @Provides
@IntoSet
fun provideGridLayout(gridLayout: InfiniteGridLayout): Pair<GridLayoutType, GridLayout> {
return Pair(InfiniteGridLayoutType, gridLayout)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconLabelVisibilityRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconLabelVisibilityRepository.kt
new file mode 100644
index 0000000..686e5f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconLabelVisibilityRepository.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.qs.panels.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Repository for whether to show the labels of icon tiles. */
+@SysUISingleton
+class IconLabelVisibilityRepository @Inject constructor() {
+ // TODO(b/341735914): Persist and back up showLabels
+ private val _showLabels = MutableStateFlow(false)
+ val showLabels: StateFlow<Boolean> = _showLabels.asStateFlow()
+
+ fun setShowLabels(showLabels: Boolean) {
+ _showLabels.value = showLabels
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt
new file mode 100644
index 0000000..a871531
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.qs.panels.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.qs.panels.data.repository.IconLabelVisibilityRepository
+import com.android.systemui.qs.panels.shared.model.IconLabelVisibilityLog
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+@SysUISingleton
+class IconLabelVisibilityInteractor
+@Inject
+constructor(
+ private val repo: IconLabelVisibilityRepository,
+ @IconLabelVisibilityLog private val logBuffer: LogBuffer,
+ @Application scope: CoroutineScope,
+) {
+ val showLabels: StateFlow<Boolean> =
+ repo.showLabels
+ .onEach { logChange(it) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), repo.showLabels.value)
+
+ fun setShowLabels(showLabels: Boolean) {
+ repo.setShowLabels(showLabels)
+ }
+
+ private fun logChange(showLabels: Boolean) {
+ logBuffer.log(
+ LOG_BUFFER_ICON_TILE_LABEL_VISIBILITY_CHANGE_TAG,
+ LogLevel.DEBUG,
+ { bool1 = showLabels },
+ { "Icon tile label visibility changed: $bool1" }
+ )
+ }
+
+ private companion object {
+ const val LOG_BUFFER_ICON_TILE_LABEL_VISIBILITY_CHANGE_TAG = "IconLabelVisibilityChange"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
new file mode 100644
index 0000000..c92234c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.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.qs.panels.shared.model
+
+import javax.inject.Qualifier
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class IconLabelVisibilityLog()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index f5ee720..4aeaa7d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -26,9 +26,9 @@
import androidx.compose.ui.res.dimensionResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
-import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.res.R
@@ -38,8 +38,8 @@
class InfiniteGridLayout
@Inject
constructor(
- private val iconTilesInteractor: IconTilesInteractor,
- private val gridSizeInteractor: InfiniteGridSizeInteractor
+ private val iconTilesViewModel: IconTilesViewModel,
+ private val gridSizeViewModel: InfiniteGridSizeViewModel,
) : GridLayout {
@Composable
@@ -52,8 +52,8 @@
tiles.forEach { it.startListening(token) }
onDispose { tiles.forEach { it.stopListening(token) } }
}
- val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
- val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
+ val iconTilesSpecs by iconTilesViewModel.iconTilesSpecs.collectAsStateWithLifecycle()
+ val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
items(
@@ -68,9 +68,9 @@
}
) { index ->
Tile(
- tiles[index],
- iconTilesSpecs.contains(tiles[index].spec),
- Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
+ tile = tiles[index],
+ iconOnly = iconTilesSpecs.contains(tiles[index].spec),
+ modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
)
}
}
@@ -83,8 +83,8 @@
onAddTile: (TileSpec, Int) -> Unit,
onRemoveTile: (TileSpec) -> Unit,
) {
- val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
- val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
+ val iconOnlySpecs by iconTilesViewModel.iconTilesSpecs.collectAsStateWithLifecycle()
+ val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
DefaultEditTileGrid(
tiles = tiles,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
index 8d0b386..708ef0d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
@@ -20,6 +20,7 @@
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -32,6 +33,8 @@
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Switch
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
@@ -39,14 +42,14 @@
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.modifiers.background
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
-import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.PartitionedGridViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -54,12 +57,8 @@
import javax.inject.Inject
@SysUISingleton
-class PartitionedGridLayout
-@Inject
-constructor(
- private val iconTilesInteractor: IconTilesInteractor,
- private val gridSizeInteractor: InfiniteGridSizeInteractor,
-) : GridLayout {
+class PartitionedGridLayout @Inject constructor(private val viewModel: PartitionedGridViewModel) :
+ GridLayout {
@Composable
override fun TileGrid(tiles: List<TileViewModel>, modifier: Modifier) {
DisposableEffect(tiles) {
@@ -67,9 +66,11 @@
tiles.forEach { it.startListening(token) }
onDispose { tiles.forEach { it.stopListening(token) } }
}
- val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
- val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
- val tileHeight = dimensionResource(id = R.dimen.qs_tile_height)
+ val iconTilesSpecs by viewModel.iconTilesSpecs.collectAsStateWithLifecycle()
+ val columns by viewModel.columns.collectAsStateWithLifecycle()
+ val showLabels by viewModel.showLabels.collectAsStateWithLifecycle()
+ val largeTileHeight = tileHeight()
+ val iconTileHeight = tileHeight(showLabels)
val (smallTiles, largeTiles) = tiles.partition { iconTilesSpecs.contains(it.spec) }
TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
@@ -78,7 +79,7 @@
Tile(
tile = largeTiles[index],
iconOnly = false,
- modifier = Modifier.height(tileHeight)
+ modifier = Modifier.height(largeTileHeight)
)
}
fillUpRow(nTiles = largeTiles.size, columns = columns / 2)
@@ -88,7 +89,8 @@
Tile(
tile = smallTiles[index],
iconOnly = true,
- modifier = Modifier.height(tileHeight)
+ showLabels = showLabels,
+ modifier = Modifier.height(iconTileHeight)
)
}
}
@@ -101,8 +103,9 @@
onAddTile: (TileSpec, Int) -> Unit,
onRemoveTile: (TileSpec) -> Unit
) {
- val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
- val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
+ val iconOnlySpecs by viewModel.iconTilesSpecs.collectAsStateWithLifecycle()
+ val columns by viewModel.columns.collectAsStateWithLifecycle()
+ val showLabels by viewModel.showLabels.collectAsStateWithLifecycle()
val (currentTiles, otherTiles) = tiles.partition { it.isCurrent }
val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
@@ -110,27 +113,56 @@
}
val isIconOnly: (TileSpec) -> Boolean =
remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } }
- val tileHeight = dimensionResource(id = R.dimen.qs_tile_height)
+ val largeTileHeight = tileHeight()
+ val iconTileHeight = tileHeight(showLabels)
val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical)
Column(
verticalArrangement = Arrangement.spacedBy(tilePadding),
modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState())
) {
+ Row(
+ modifier =
+ Modifier.background(
+ color = MaterialTheme.colorScheme.surfaceVariant,
+ alpha = { 1f },
+ shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius))
+ )
+ .padding(tilePadding)
+ ) {
+ Column(Modifier.padding(start = tilePadding)) {
+ Text(
+ text = "Show text labels",
+ color = MaterialTheme.colorScheme.onBackground,
+ fontWeight = FontWeight.Bold
+ )
+ Text(
+ text = "Display names under each tile",
+ color = MaterialTheme.colorScheme.onBackground
+ )
+ }
+ Spacer(modifier = Modifier.weight(1f))
+ Switch(checked = showLabels, onCheckedChange = { viewModel.setShowLabels(it) })
+ }
+
CurrentTiles(
tiles = currentTiles,
- tileHeight = tileHeight,
+ largeTileHeight = largeTileHeight,
+ iconTileHeight = iconTileHeight,
tilePadding = tilePadding,
onRemoveTile = onRemoveTile,
isIconOnly = isIconOnly,
columns = columns,
+ showLabels = showLabels,
)
AvailableTiles(
tiles = otherTiles,
- tileHeight = tileHeight,
+ largeTileHeight = largeTileHeight,
+ iconTileHeight = iconTileHeight,
tilePadding = tilePadding,
addTileToEnd = addTileToEnd,
isIconOnly = isIconOnly,
+ showLabels = showLabels,
columns = columns,
)
}
@@ -139,23 +171,31 @@
@Composable
private fun CurrentTiles(
tiles: List<EditTileViewModel>,
- tileHeight: Dp,
+ largeTileHeight: Dp,
+ iconTileHeight: Dp,
tilePadding: Dp,
onRemoveTile: (TileSpec) -> Unit,
isIconOnly: (TileSpec) -> Boolean,
+ showLabels: Boolean,
columns: Int,
) {
val (smallTiles, largeTiles) = tiles.partition { isIconOnly(it.tileSpec) }
- val largeGridHeight = gridHeight(largeTiles.size, tileHeight, columns / 2, tilePadding)
- val smallGridHeight = gridHeight(smallTiles.size, tileHeight, columns, tilePadding)
+ val largeGridHeight = gridHeight(largeTiles.size, largeTileHeight, columns / 2, tilePadding)
+ val smallGridHeight = gridHeight(smallTiles.size, iconTileHeight, columns, tilePadding)
CurrentTilesContainer {
TileLazyGrid(
columns = GridCells.Fixed(columns),
modifier = Modifier.height(largeGridHeight),
) {
- editTiles(largeTiles, ClickAction.REMOVE, onRemoveTile, { false }, true)
+ editTiles(
+ largeTiles,
+ ClickAction.REMOVE,
+ onRemoveTile,
+ { false },
+ indicatePosition = true
+ )
}
}
CurrentTilesContainer {
@@ -163,7 +203,14 @@
columns = GridCells.Fixed(columns),
modifier = Modifier.height(smallGridHeight),
) {
- editTiles(smallTiles, ClickAction.REMOVE, onRemoveTile, { true }, true)
+ editTiles(
+ smallTiles,
+ ClickAction.REMOVE,
+ onRemoveTile,
+ { true },
+ showLabels = showLabels,
+ indicatePosition = true
+ )
}
}
}
@@ -171,19 +218,21 @@
@Composable
private fun AvailableTiles(
tiles: List<EditTileViewModel>,
- tileHeight: Dp,
+ largeTileHeight: Dp,
+ iconTileHeight: Dp,
tilePadding: Dp,
addTileToEnd: (TileSpec) -> Unit,
isIconOnly: (TileSpec) -> Boolean,
+ showLabels: Boolean,
columns: Int,
) {
val (tilesStock, tilesCustom) = tiles.partition { it.appName == null }
val (smallTiles, largeTiles) = tilesStock.partition { isIconOnly(it.tileSpec) }
- val largeGridHeight = gridHeight(largeTiles.size, tileHeight, columns / 2, tilePadding)
- val smallGridHeight = gridHeight(smallTiles.size, tileHeight, columns, tilePadding)
+ val largeGridHeight = gridHeight(largeTiles.size, largeTileHeight, columns / 2, tilePadding)
+ val smallGridHeight = gridHeight(smallTiles.size, iconTileHeight, columns, tilePadding)
val largeGridHeightCustom =
- gridHeight(tilesCustom.size, tileHeight, columns / 2, tilePadding)
+ gridHeight(tilesCustom.size, largeTileHeight, columns / 2, tilePadding)
// Add up the height of all three grids and add padding in between
val gridHeight =
@@ -199,7 +248,13 @@
fillUpRow(nTiles = largeTiles.size, columns = columns / 2)
// Small tiles
- editTiles(smallTiles, ClickAction.ADD, addTileToEnd, isIconOnly)
+ editTiles(
+ smallTiles,
+ ClickAction.ADD,
+ addTileToEnd,
+ isIconOnly,
+ showLabels = showLabels
+ )
fillUpRow(nTiles = smallTiles.size, columns = columns)
// Custom tiles, all large
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
index ddd97c2..70d629f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
@@ -27,11 +27,11 @@
import androidx.compose.ui.res.dimensionResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
-import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor
import com.android.systemui.qs.panels.shared.model.SizedTile
import com.android.systemui.qs.panels.shared.model.TileRow
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.res.R
@@ -41,8 +41,8 @@
class StretchedGridLayout
@Inject
constructor(
- private val iconTilesInteractor: IconTilesInteractor,
- private val gridSizeInteractor: InfiniteGridSizeInteractor,
+ private val iconTilesViewModel: IconTilesViewModel,
+ private val gridSizeViewModel: InfiniteGridSizeViewModel,
) : GridLayout {
@Composable
@@ -60,7 +60,7 @@
// Icon [3 | 4]
// Large [6 | 8]
val columns = 12
- val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
+ val iconTilesSpecs by iconTilesViewModel.iconTilesSpecs.collectAsStateWithLifecycle()
val stretchedTiles =
remember(tiles) {
val sizedTiles =
@@ -80,9 +80,9 @@
TileLazyGrid(columns = GridCells.Fixed(columns), modifier = modifier) {
items(stretchedTiles.size, span = { GridItemSpan(stretchedTiles[it].width) }) { index ->
Tile(
- stretchedTiles[index].tile,
- iconTilesSpecs.contains(stretchedTiles[index].tile.spec),
- Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
+ tile = stretchedTiles[index].tile,
+ iconOnly = iconTilesSpecs.contains(stretchedTiles[index].tile.spec),
+ modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
)
}
}
@@ -95,8 +95,8 @@
onAddTile: (TileSpec, Int) -> Unit,
onRemoveTile: (TileSpec) -> Unit
) {
- val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
- val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
+ val iconOnlySpecs by iconTilesViewModel.iconTilesSpecs.collectAsStateWithLifecycle()
+ val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
DefaultEditTileGrid(
tiles = tiles,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index e8c65a5..a6838c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -69,6 +69,9 @@
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.stateDescription
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.Expandable
@@ -98,6 +101,7 @@
fun Tile(
tile: TileViewModel,
iconOnly: Boolean,
+ showLabels: Boolean = false,
modifier: Modifier,
) {
val state: TileUiState by
@@ -136,7 +140,8 @@
secondaryLabel = state.secondaryLabel.toString(),
icon = icon,
colors = state.colors,
- iconOnly = iconOnly
+ iconOnly = iconOnly,
+ showLabels = showLabels,
)
}
}
@@ -213,6 +218,7 @@
clickAction: ClickAction,
onClick: (TileSpec) -> Unit,
isIconOnly: (TileSpec) -> Boolean,
+ showLabels: Boolean = false,
indicatePosition: Boolean = false,
) {
items(
@@ -250,10 +256,13 @@
this.stateDescription = stateDescription
}
) {
+ val iconOnly = isIconOnly(viewModel.tileSpec)
+ val tileHeight = tileHeight(iconOnly && showLabels)
EditTile(
tileViewModel = viewModel,
- isIconOnly(viewModel.tileSpec),
- modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
+ iconOnly = iconOnly,
+ showLabels = showLabels,
+ modifier = Modifier.height(tileHeight)
)
if (canClick) {
Badge(clickAction, Modifier.align(Alignment.TopEnd))
@@ -281,6 +290,7 @@
fun EditTile(
tileViewModel: EditTileViewModel,
iconOnly: Boolean,
+ showLabels: Boolean,
modifier: Modifier = Modifier,
) {
val label = tileViewModel.label.load() ?: tileViewModel.tileSpec.spec
@@ -297,6 +307,7 @@
colors = colors,
icon = tileViewModel.icon,
iconOnly = iconOnly,
+ showLabels = showLabels,
animateIconToEnd = true,
)
}
@@ -380,9 +391,26 @@
icon: Icon,
colors: TileColorAttributes,
iconOnly: Boolean,
+ showLabels: Boolean = false,
animateIconToEnd: Boolean = false,
) {
- TileIcon(icon, colorAttr(colors.icon), animateIconToEnd)
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ modifier = Modifier.fillMaxHeight()
+ ) {
+ TileIcon(icon, colorAttr(colors.icon), animateIconToEnd)
+
+ if (iconOnly && showLabels) {
+ Text(
+ label,
+ maxLines = 2,
+ color = colorAttr(colors.label),
+ overflow = TextOverflow.Ellipsis,
+ textAlign = TextAlign.Center,
+ )
+ }
+ }
if (!iconOnly) {
Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) {
@@ -401,3 +429,16 @@
}
}
}
+
+@Composable
+fun tileHeight(iconWithLabel: Boolean = false): Dp {
+ return if (iconWithLabel) {
+ TileDimensions.IconTileWithLabelHeight
+ } else {
+ dimensionResource(id = R.dimen.qs_tile_height)
+ }
+}
+
+private object TileDimensions {
+ val IconTileWithLabelHeight = 100.dp
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModel.kt
new file mode 100644
index 0000000..5d4b8f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModel.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.qs.panels.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.domain.interactor.IconLabelVisibilityInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+interface IconLabelVisibilityViewModel {
+ val showLabels: StateFlow<Boolean>
+
+ fun setShowLabels(showLabels: Boolean)
+}
+
+@SysUISingleton
+class IconLabelVisibilityViewModelImpl
+@Inject
+constructor(private val interactor: IconLabelVisibilityInteractor) : IconLabelVisibilityViewModel {
+ override val showLabels: StateFlow<Boolean> = interactor.showLabels
+
+ override fun setShowLabels(showLabels: Boolean) {
+ interactor.setShowLabels(showLabels)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
new file mode 100644
index 0000000..9ad00c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.qs.panels.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+interface IconTilesViewModel {
+ val iconTilesSpecs: StateFlow<Set<TileSpec>>
+}
+
+@SysUISingleton
+class IconTilesViewModelImpl @Inject constructor(interactor: IconTilesInteractor) :
+ IconTilesViewModel {
+ override val iconTilesSpecs = interactor.iconTilesSpecs
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridSizeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridSizeViewModel.kt
new file mode 100644
index 0000000..a4ee58f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridSizeViewModel.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+interface InfiniteGridSizeViewModel {
+ val columns: StateFlow<Int>
+}
+
+@SysUISingleton
+class InfiniteGridSizeViewModelImpl @Inject constructor(interactor: InfiniteGridSizeInteractor) :
+ InfiniteGridSizeViewModel {
+ override val columns: StateFlow<Int> = interactor.columns
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModel.kt
new file mode 100644
index 0000000..730cf63
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModel.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class PartitionedGridViewModel
+@Inject
+constructor(
+ iconTilesViewModel: IconTilesViewModel,
+ gridSizeViewModel: InfiniteGridSizeViewModel,
+ iconLabelVisibilityViewModel: IconLabelVisibilityViewModel,
+) :
+ IconTilesViewModel by iconTilesViewModel,
+ InfiniteGridSizeViewModel by gridSizeViewModel,
+ IconLabelVisibilityViewModel by iconLabelVisibilityViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 56588ff..8887f58 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -677,6 +677,10 @@
mId = id;
}
+ public int getResourceId() {
+ return mId;
+ }
+
@Override
public boolean equals(Object o) {
return o instanceof DrawableIconWithRes && ((DrawableIconWithRes) o).mId == mId;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 76aa146..f218d86 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -16,8 +16,13 @@
package com.android.systemui.qs.tiles;
-import static android.graphics.drawable.Icon.TYPE_URI;
import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT;
+import static android.graphics.drawable.Icon.TYPE_URI;
+import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
+import static android.graphics.drawable.Icon.TYPE_RESOURCE;
+import static android.graphics.drawable.Icon.TYPE_BITMAP;
+import static android.graphics.drawable.Icon.TYPE_ADAPTIVE_BITMAP;
+import static android.graphics.drawable.Icon.TYPE_DATA;
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE;
@@ -237,11 +242,21 @@
return;
}
mSelectedCard = cards.get(selectedIndex);
- android.graphics.drawable.Icon cardImageIcon = mSelectedCard.getCardImage();
- if (cardImageIcon.getType() == TYPE_URI) {
- mCardViewDrawable = null;
- } else {
- mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext);
+ switch (mSelectedCard.getCardImage().getType()) {
+ case TYPE_URI:
+ case TYPE_URI_ADAPTIVE_BITMAP:
+ mCardViewDrawable = null;
+ break;
+ case TYPE_RESOURCE:
+ case TYPE_BITMAP:
+ case TYPE_ADAPTIVE_BITMAP:
+ case TYPE_DATA:
+ mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext);
+ break;
+ default:
+ Log.e(TAG, "Unknown icon type: " + mSelectedCard.getCardImage().getType());
+ mCardViewDrawable = null;
+ break;
}
refreshState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
index c0fc52e..f088943 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
@@ -18,6 +18,7 @@
import android.content.res.Resources
import android.content.res.Resources.Theme
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel
@@ -82,7 +83,8 @@
secondaryLabel = resources.getString(R.string.qs_alarm_tile_no_alarm)
}
}
-
+ iconRes = R.drawable.ic_alarm
+ icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
sideViewIcon = QSTileState.SideViewIcon.Chevron
contentDescription = label
supportedActions = setOf(QSTileState.UserAction.CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
index 0c08fba..bcf0935 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
@@ -38,17 +38,10 @@
QSTileState.build(resources, theme, config.uiConfig) {
label = resources.getString(R.string.battery_detail_switch_title)
contentDescription = label
-
- icon = {
- Icon.Loaded(
- resources.getDrawable(
- if (data.isPowerSaving) R.drawable.qs_battery_saver_icon_on
- else R.drawable.qs_battery_saver_icon_off,
- theme
- ),
- null
- )
- }
+ iconRes =
+ if (data.isPowerSaving) R.drawable.qs_battery_saver_icon_on
+ else R.drawable.qs_battery_saver_icon_off
+ icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
sideViewIcon = QSTileState.SideViewIcon.None
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
index 1efbfd7..cad7c65 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
@@ -37,6 +37,8 @@
QSTileState.build(resources, theme, config.uiConfig) {
val subtitleArray = resources.getStringArray(R.array.tile_states_color_correction)
+ iconRes = R.drawable.ic_qs_color_correction
+
if (data.isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
secondaryLabel = subtitleArray[2]
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
index 58e7613..d7d6124 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
@@ -37,14 +37,16 @@
override fun map(config: QSTileConfig, data: FlashlightTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
+ iconRes =
+ if (data is FlashlightTileModel.FlashlightAvailable && data.isEnabled) {
+ R.drawable.qs_flashlight_icon_on
+ } else {
+ R.drawable.qs_flashlight_icon_off
+ }
val icon =
Icon.Loaded(
resources.getDrawable(
- if (data is FlashlightTileModel.FlashlightAvailable && data.isEnabled) {
- R.drawable.qs_flashlight_icon_on
- } else {
- R.drawable.qs_flashlight_icon_off
- },
+ iconRes!!,
theme,
),
contentDescription = null
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
index 26069c7..6b4dda1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
@@ -36,10 +36,11 @@
override fun map(config: QSTileConfig, data: FontScalingTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
+ iconRes = R.drawable.ic_qs_font_scaling
val icon =
Icon.Loaded(
resources.getDrawable(
- R.drawable.ic_qs_font_scaling,
+ iconRes!!,
theme,
),
contentDescription = null
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
index caae4d2..e543e4b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
@@ -53,6 +53,7 @@
stateDescription = data.stateDescription.loadContentDescription(context)
contentDescription = data.contentDescription.loadContentDescription(context)
+ iconRes = data.iconId
if (data.icon != null) {
this.icon = { data.icon }
} else if (data.iconId != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
index 4af9854..40aee65 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
@@ -41,22 +41,13 @@
if (data.isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
secondaryLabel = subtitleArray[2]
- icon = {
- Icon.Loaded(
- resources.getDrawable(R.drawable.qs_invert_colors_icon_on, theme),
- null
- )
- }
+ iconRes = R.drawable.qs_invert_colors_icon_on
} else {
activationState = QSTileState.ActivationState.INACTIVE
secondaryLabel = subtitleArray[1]
- icon = {
- Icon.Loaded(
- resources.getDrawable(R.drawable.qs_invert_colors_icon_off, theme),
- null
- )
- }
+ iconRes = R.drawable.qs_invert_colors_icon_off
}
+ icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
contentDescription = label
supportedActions =
setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
index fe5445d..d58f5ab 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
@@ -37,14 +37,16 @@
override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
+ iconRes =
+ if (data.isEnabled) {
+ R.drawable.qs_location_icon_on
+ } else {
+ R.drawable.qs_location_icon_off
+ }
val icon =
Icon.Loaded(
resources.getDrawable(
- if (data.isEnabled) {
- R.drawable.qs_location_icon_on
- } else {
- R.drawable.qs_location_icon_off
- },
+ iconRes!!,
theme,
),
contentDescription = null
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
index 5c2dcfc..bcf7cc7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
@@ -52,21 +52,14 @@
if (data.isActivated) {
activationState = QSTileState.ActivationState.ACTIVE
- val loadedIcon =
- Icon.Loaded(
- resources.getDrawable(R.drawable.qs_nightlight_icon_on, theme),
- contentDescription = null
- )
- icon = { loadedIcon }
+ iconRes = R.drawable.qs_nightlight_icon_on
} else {
activationState = QSTileState.ActivationState.INACTIVE
- val loadedIcon =
- Icon.Loaded(
- resources.getDrawable(R.drawable.qs_nightlight_icon_off, theme),
- contentDescription = null
- )
- icon = { loadedIcon }
+ iconRes = R.drawable.qs_nightlight_icon_off
}
+ val loadedIcon =
+ Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
+ icon = { loadedIcon }
secondaryLabel = getSecondaryLabel(data, resources)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
index 9166ed8..4080996 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
@@ -38,15 +38,8 @@
QSTileState.build(resources, theme, config.uiConfig) {
val subtitleArray = resources.getStringArray(R.array.tile_states_onehanded)
label = resources.getString(R.string.quick_settings_onehanded_label)
- icon = {
- Icon.Loaded(
- resources.getDrawable(
- com.android.internal.R.drawable.ic_qs_one_handed_mode,
- theme
- ),
- null
- )
- }
+ iconRes = com.android.internal.R.drawable.ic_qs_one_handed_mode
+ icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
if (data.isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
secondaryLabel = subtitleArray[2]
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
index 45a7717..8231742 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
@@ -38,9 +38,8 @@
QSTileState.build(resources, theme, config.uiConfig) {
label = resources.getString(R.string.qr_code_scanner_title)
contentDescription = label
- icon = {
- Icon.Loaded(resources.getDrawable(R.drawable.ic_qr_code_scanner, theme), null)
- }
+ iconRes = R.drawable.ic_qr_code_scanner
+ icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
sideViewIcon = QSTileState.SideViewIcon.Chevron
supportedActions = setOf(QSTileState.UserAction.CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
index fca93df..85ee022 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
@@ -39,28 +39,23 @@
QSTileState.build(resources, theme, config.uiConfig) {
if (data.isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
- icon = {
- Icon.Loaded(
- drawable = resources.getDrawable(R.drawable.qs_extra_dim_icon_on, theme),
- contentDescription = null
- )
- }
-
+ iconRes = R.drawable.qs_extra_dim_icon_on
secondaryLabel =
resources
.getStringArray(R.array.tile_states_reduce_brightness)[Tile.STATE_ACTIVE]
} else {
activationState = QSTileState.ActivationState.INACTIVE
- icon = {
- Icon.Loaded(
- drawable = resources.getDrawable(R.drawable.qs_extra_dim_icon_off, theme),
- contentDescription = null
- )
- }
+ iconRes = R.drawable.qs_extra_dim_icon_off
secondaryLabel =
resources
.getStringArray(R.array.tile_states_reduce_brightness)[Tile.STATE_INACTIVE]
}
+ icon = {
+ Icon.Loaded(
+ drawable = resources.getDrawable(iconRes!!, theme),
+ contentDescription = null
+ )
+ }
label =
resources.getString(com.android.internal.R.string.reduce_bright_colors_feature_name)
contentDescription = label
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
index 070cdef..8e80fb0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
@@ -44,12 +44,7 @@
if (data.isRotationLocked) {
activationState = QSTileState.ActivationState.INACTIVE
this.secondaryLabel = EMPTY_SECONDARY_STRING
- this.icon = {
- Icon.Loaded(
- resources.getDrawable(R.drawable.qs_auto_rotate_icon_off, theme),
- contentDescription = null
- )
- }
+ iconRes = R.drawable.qs_auto_rotate_icon_off
} else {
activationState = QSTileState.ActivationState.ACTIVE
this.secondaryLabel =
@@ -58,12 +53,10 @@
} else {
EMPTY_SECONDARY_STRING
}
- this.icon = {
- Icon.Loaded(
- resources.getDrawable(R.drawable.qs_auto_rotate_icon_on, theme),
- contentDescription = null
- )
- }
+ this.iconRes = R.drawable.qs_auto_rotate_icon_on
+ }
+ this.icon = {
+ Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
}
if (isDeviceFoldable()) {
this.secondaryLabel = getSecondaryLabelWithPosture(this.activationState)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
index df25600..888bba87 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
@@ -36,7 +36,6 @@
override fun map(config: QSTileConfig, data: DataSaverTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
with(data) {
- val iconRes: Int
if (isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
iconRes = R.drawable.qs_data_saver_icon_on
@@ -47,7 +46,7 @@
secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[1]
}
val loadedIcon =
- Icon.Loaded(resources.getDrawable(iconRes, theme), contentDescription = null)
+ Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
icon = { loadedIcon }
contentDescription = label
supportedActions =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
index b58774b..7446708 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
@@ -42,9 +42,10 @@
when (data) {
is ScreenRecordModel.Recording -> {
activationState = QSTileState.ActivationState.ACTIVE
+ iconRes = R.drawable.qs_screen_record_icon_on
val loadedIcon =
Icon.Loaded(
- resources.getDrawable(R.drawable.qs_screen_record_icon_on, theme),
+ resources.getDrawable(iconRes!!, theme),
contentDescription = null
)
icon = { loadedIcon }
@@ -53,9 +54,10 @@
}
is ScreenRecordModel.Starting -> {
activationState = QSTileState.ActivationState.ACTIVE
+ iconRes = R.drawable.qs_screen_record_icon_on
val loadedIcon =
Icon.Loaded(
- resources.getDrawable(R.drawable.qs_screen_record_icon_on, theme),
+ resources.getDrawable(iconRes!!, theme),
contentDescription = null
)
icon = { loadedIcon }
@@ -65,9 +67,10 @@
}
is ScreenRecordModel.DoingNothing -> {
activationState = QSTileState.ActivationState.INACTIVE
+ iconRes = R.drawable.qs_screen_record_icon_off
val loadedIcon =
Icon.Loaded(
- resources.getDrawable(R.drawable.qs_screen_record_icon_off, theme),
+ resources.getDrawable(iconRes!!, theme),
contentDescription = null
)
icon = { loadedIcon }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
index 52622d2..597cf27 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
@@ -50,15 +50,8 @@
contentDescription = label
supportedActions =
setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
- icon = {
- Icon.Loaded(
- resources.getDrawable(
- sensorPrivacyTileResources.getIconRes(data.isBlocked),
- theme
- ),
- null
- )
- }
+ iconRes = sensorPrivacyTileResources.getIconRes(data.isBlocked)
+ icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
sideViewIcon = QSTileState.SideViewIcon.None
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
index ffef2b6..f29c745d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
@@ -117,12 +117,12 @@
}
}
- val iconRes =
+ iconRes =
if (activationState == QSTileState.ActivationState.ACTIVE)
R.drawable.qs_light_dark_theme_icon_on
else R.drawable.qs_light_dark_theme_icon_off
val loadedIcon =
- Icon.Loaded(resources.getDrawable(iconRes, theme), contentDescription = null)
+ Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
icon = { loadedIcon }
supportedActions =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
index 55445bb..eee95b7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
@@ -41,15 +41,9 @@
QSTileState.build(resources, theme, config.uiConfig) {
label = getTileLabel()!!
contentDescription = label
-
+ iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status
icon = {
- Icon.Loaded(
- resources.getDrawable(
- com.android.internal.R.drawable.stat_sys_managed_profile_status,
- theme
- ),
- contentDescription = null
- )
+ Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
}
when (data) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index b927e41..ae6c014 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -29,10 +29,14 @@
* [QSTileState.build] for better state creation experience and preset default values for certain
* fields.
*
+ * @param iconRes For when we want to have Loaded icon, but still keep a reference to the resource
+ * id. A use case would be for tests that have to compare animated drawables.
+ *
* // TODO(b/http://b/299909989): Clean up legacy mappings after the transition
*/
data class QSTileState(
val icon: () -> Icon?,
+ val iconRes: Int?,
val label: CharSequence,
val activationState: ActivationState,
val secondaryLabel: CharSequence?,
@@ -111,6 +115,7 @@
var icon: () -> Icon?,
var label: CharSequence,
) {
+ var iconRes: Int? = null
var activationState: ActivationState = ActivationState.INACTIVE
var secondaryLabel: CharSequence? = null
var supportedActions: Set<UserAction> = setOf(UserAction.CLICK)
@@ -123,6 +128,7 @@
fun build(): QSTileState =
QSTileState(
icon,
+ iconRes,
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index 5346b23..7be13e0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -28,6 +28,7 @@
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -241,7 +242,9 @@
iconSupplier = Supplier {
when (val stateIcon = viewModelState.icon()) {
- is Icon.Loaded -> DrawableIcon(stateIcon.drawable)
+ is Icon.Loaded ->
+ if (viewModelState.iconRes == null) DrawableIcon(stateIcon.drawable)
+ else DrawableIconWithRes(stateIcon.drawable, viewModelState.iconRes)
is Icon.Resource -> ResourceIcon.get(stateIcon.res)
null -> null
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index fb872d5..c7326b08 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -46,7 +46,6 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.BufferOverflow
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -89,8 +88,10 @@
/**
* A view with the QS content ([QSContainerImpl]), managed by an instance of [QSImpl] tracked by
* the interactor.
+ *
+ * A null value means that there is no inflated view yet. See [inflate].
*/
- val qsView: Flow<View>
+ val qsView: StateFlow<View?>
/** Sets the [MirrorController] in [QSImpl]. Set to `null` to remove. */
fun setBrightnessMirrorController(mirrorController: MirrorController?)
@@ -101,9 +102,22 @@
*/
suspend fun inflate(context: Context)
- /** Set the current state for QS. [state]. */
+ /**
+ * Set the current state for QS. [state].
+ *
+ * This will not trigger expansion (animation between QQS or QS) or squishiness to be applied.
+ * For that, use [applyLatestExpansionAndSquishiness] outside of the composition phase.
+ */
fun setState(state: State)
+ /**
+ * Explicitly applies the expansion and squishiness value from the latest state set. Call this
+ * only outside of the composition phase as this will call [QSImpl.setQsExpansion] that is
+ * normally called during animations. In particular, this will read the value of
+ * [State.squishiness], that is not safe to read in the composition phase.
+ */
+ fun applyLatestExpansionAndSquishiness()
+
/** Propagates the bottom nav bar size to [QSImpl] to be used as necessary. */
suspend fun applyBottomNavBarPadding(padding: Int)
@@ -141,14 +155,24 @@
override val squishiness = { 1f }
}
- /** State for appearing QQS from Lockscreen or Gone */
- data class UnsquishingQQS(override val squishiness: () -> Float) : State {
+ /**
+ * State for appearing QQS from Lockscreen or Gone.
+ *
+ * This should not be a data class, as it has a method parameter and even if it's the same
+ * lambda the output value may have changed.
+ */
+ class UnsquishingQQS(override val squishiness: () -> Float) : State {
override val isVisible = true
override val expansion = 0f
}
- /** State for appearing QS from Lockscreen or Gone, used in Split shade */
- data class UnsquishingQS(override val squishiness: () -> Float) : State {
+ /**
+ * State for appearing QS from Lockscreen or Gone, used in Split shade.
+ *
+ * This should not be a data class, as it has a method parameter and even if it's the same
+ * lambda the output value may have changed.
+ */
+ class UnsquishingQS(override val squishiness: () -> Float) : State {
override val isVisible = true
override val expansion = 1f
}
@@ -236,7 +260,10 @@
private val _qsImpl: MutableStateFlow<QSImpl?> = MutableStateFlow(null)
val qsImpl = _qsImpl.asStateFlow()
- override val qsView: Flow<View> = _qsImpl.map { it?.view }.filterNotNull()
+ override val qsView: StateFlow<View?> =
+ _qsImpl
+ .map { it?.view }
+ .stateIn(applicationScope, SharingStarted.WhileSubscribed(), _qsImpl.value?.view)
override val qqsHeight: Int
get() = qsImpl.value?.qqsHeight ?: 0
@@ -370,7 +397,12 @@
setQsVisible(state.isVisible)
setExpanded(state.isVisible && state.expansion > 0f)
setListening(state.isVisible)
- setQsExpansion(state.expansion, 1f, 0f, state.squishiness())
+ }
+
+ override fun applyLatestExpansionAndSquishiness() {
+ val qsImpl = _qsImpl.value
+ val state = state.value
+ qsImpl?.setQsExpansion(state.expansion, 1f, 0f, state.squishiness())
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index b971781..bccbb11 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -2,12 +2,14 @@
import android.content.Context
import android.util.AttributeSet
+import android.view.MotionEvent
import android.view.View
import android.view.WindowInsets
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import com.android.systemui.shade.TouchLogger
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import kotlinx.coroutines.flow.MutableStateFlow
@@ -60,4 +62,16 @@
this.windowInsets.value = windowInsets
return windowInsets
}
+
+ override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
+ viewModel.onMotionEvent(ev)
+ return super.dispatchTouchEvent(ev).also {
+ TouchLogger.logDispatchTouch(TAG, ev, it)
+ viewModel.onMotionEventComplete()
+ }
+ }
+
+ companion object {
+ private const val TAG = "SceneWindowRootView"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 5469a4e..e024710 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -119,6 +119,7 @@
IMediaProjection projection = IMediaProjection.Stub.asInterface(proj.asBinder());
if (mCaptureRegion != null) {
projection.setLaunchCookie(mCaptureRegion.getLaunchCookie());
+ projection.setTaskId(mCaptureRegion.getTaskId());
}
mMediaProjection = new MediaProjection(mContext, projection);
mMediaProjection.registerCallback(this, mHandler);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index 4ab0918..9e62280 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -90,12 +90,18 @@
)
}
systemUiProxy.dismissKeyguard()
- transitionCoordinator?.startExit()
+ var transitionOptions: ActivityOptions? = null
+ if (transitionCoordinator?.decor?.isAttachedToWindow == true) {
+ transitionCoordinator.startExit()
+ transitionOptions = options
+ }
if (user == myUserHandle()) {
- withContext(mainDispatcher) { context.startActivity(intent, options?.toBundle()) }
+ withContext(mainDispatcher) {
+ context.startActivity(intent, transitionOptions?.toBundle())
+ }
} else {
- launchCrossProfileIntent(user, intent, options?.toBundle())
+ launchCrossProfileIntent(user, intent, transitionOptions?.toBundle())
}
if (overrideTransition) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsController.kt
new file mode 100644
index 0000000..2ffb783
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsController.kt
@@ -0,0 +1,116 @@
+/*
+ * 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.screenshot
+
+import android.app.assist.AssistContent
+import com.android.systemui.screenshot.ui.viewmodel.ActionButtonAppearance
+import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.UUID
+
+/**
+ * Responsible for obtaining the actions for each screenshot and sending them to the view model.
+ * Ensures that only actions from screenshots that are currently being shown are added to the view
+ * model.
+ */
+class ScreenshotActionsController
+@AssistedInject
+constructor(
+ private val viewModel: ScreenshotViewModel,
+ private val actionsProviderFactory: ScreenshotActionsProvider.Factory,
+ @Assisted val actionExecutor: ActionExecutor
+) {
+ private val actionProviders: MutableMap<UUID, ScreenshotActionsProvider> = mutableMapOf()
+ private var currentScreenshotId: UUID? = null
+
+ fun setCurrentScreenshot(screenshot: ScreenshotData): UUID {
+ val screenshotId = UUID.randomUUID()
+ currentScreenshotId = screenshotId
+ actionProviders[screenshotId] =
+ actionsProviderFactory.create(
+ screenshotId,
+ screenshot,
+ actionExecutor,
+ ActionsCallback(screenshotId),
+ )
+ return screenshotId
+ }
+
+ fun endScreenshotSession() {
+ currentScreenshotId = null
+ }
+
+ fun onAssistContent(screenshotId: UUID, assistContent: AssistContent?) {
+ actionProviders[screenshotId]?.onAssistContent(assistContent)
+ }
+
+ fun onScrollChipReady(screenshotId: UUID, onClick: Runnable) {
+ if (screenshotId == currentScreenshotId) {
+ actionProviders[screenshotId]?.onScrollChipReady(onClick)
+ }
+ }
+
+ fun onScrollChipInvalidated() {
+ for (provider in actionProviders.values) {
+ provider.onScrollChipInvalidated()
+ }
+ }
+
+ fun setCompletedScreenshot(screenshotId: UUID, result: ScreenshotSavedResult) {
+ if (screenshotId == currentScreenshotId) {
+ actionProviders[screenshotId]?.setCompletedScreenshot(result)
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun getController(actionExecutor: ActionExecutor): ScreenshotActionsController
+ }
+
+ inner class ActionsCallback(private val screenshotId: UUID) {
+ fun providePreviewAction(onClick: () -> Unit) {
+ if (screenshotId == currentScreenshotId) {
+ viewModel.setPreviewAction(onClick)
+ }
+ }
+
+ fun provideActionButton(
+ appearance: ActionButtonAppearance,
+ showDuringEntrance: Boolean,
+ onClick: () -> Unit
+ ): Int {
+ if (screenshotId == currentScreenshotId) {
+ return viewModel.addAction(appearance, showDuringEntrance, onClick)
+ }
+ return 0
+ }
+
+ fun updateActionButtonAppearance(buttonId: Int, appearance: ActionButtonAppearance) {
+ if (screenshotId == currentScreenshotId) {
+ viewModel.updateActionAppearance(buttonId, appearance)
+ }
+ }
+
+ fun updateActionButtonVisibility(buttonId: Int, visible: Boolean) {
+ if (screenshotId == currentScreenshotId) {
+ viewModel.setActionVisibility(buttonId, visible)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
index a1dd415..b8029c8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
@@ -29,10 +29,10 @@
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SHARE_TAPPED
import com.android.systemui.screenshot.ui.viewmodel.ActionButtonAppearance
-import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import java.util.UUID
/**
* Provides actions for screenshots. This class can be overridden by a vendor-specific SysUI
@@ -51,9 +51,10 @@
interface Factory {
fun create(
+ requestId: UUID,
request: ScreenshotData,
- requestId: String,
actionExecutor: ActionExecutor,
+ actionsCallback: ScreenshotActionsController.ActionsCallback,
): ScreenshotActionsProvider
}
}
@@ -62,11 +63,11 @@
@AssistedInject
constructor(
private val context: Context,
- private val viewModel: ScreenshotViewModel,
private val uiEventLogger: UiEventLogger,
+ @Assisted val requestId: UUID,
@Assisted val request: ScreenshotData,
- @Assisted val requestId: String,
@Assisted val actionExecutor: ActionExecutor,
+ @Assisted val actionsCallback: ScreenshotActionsController.ActionsCallback,
) : ScreenshotActionsProvider {
private var addedScrollChip = false
private var onScrollClick: Runnable? = null
@@ -74,7 +75,7 @@
private var result: ScreenshotSavedResult? = null
init {
- viewModel.setPreviewAction {
+ actionsCallback.providePreviewAction {
debugLog(LogConfig.DEBUG_ACTIONS) { "Preview tapped" }
uiEventLogger.log(SCREENSHOT_PREVIEW_TAPPED, 0, request.packageNameString)
onDeferrableActionTapped { result ->
@@ -85,26 +86,8 @@
)
}
}
- viewModel.addAction(
- ActionButtonAppearance(
- AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_edit),
- context.resources.getString(R.string.screenshot_edit_label),
- context.resources.getString(R.string.screenshot_edit_description),
- ),
- showDuringEntrance = true,
- ) {
- debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" }
- uiEventLogger.log(SCREENSHOT_EDIT_TAPPED, 0, request.packageNameString)
- onDeferrableActionTapped { result ->
- actionExecutor.startSharedTransition(
- createEdit(result.uri, context),
- result.user,
- true
- )
- }
- }
- viewModel.addAction(
+ actionsCallback.provideActionButton(
ActionButtonAppearance(
AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_share),
context.resources.getString(R.string.screenshot_share_label),
@@ -122,12 +105,31 @@
)
}
}
+
+ actionsCallback.provideActionButton(
+ ActionButtonAppearance(
+ AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_edit),
+ context.resources.getString(R.string.screenshot_edit_label),
+ context.resources.getString(R.string.screenshot_edit_description),
+ ),
+ showDuringEntrance = true,
+ ) {
+ debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" }
+ uiEventLogger.log(SCREENSHOT_EDIT_TAPPED, 0, request.packageNameString)
+ onDeferrableActionTapped { result ->
+ actionExecutor.startSharedTransition(
+ createEdit(result.uri, context),
+ result.user,
+ true
+ )
+ }
+ }
}
override fun onScrollChipReady(onClick: Runnable) {
onScrollClick = onClick
if (!addedScrollChip) {
- viewModel.addAction(
+ actionsCallback.provideActionButton(
ActionButtonAppearance(
AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_scroll),
context.resources.getString(R.string.screenshot_scroll_label),
@@ -161,9 +163,10 @@
@AssistedFactory
interface Factory : ScreenshotActionsProvider.Factory {
override fun create(
+ requestId: UUID,
request: ScreenshotData,
- requestId: String,
actionExecutor: ActionExecutor,
+ actionsCallback: ScreenshotActionsController.ActionsCallback,
): DefaultScreenshotActionsProvider
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 4c60090..e8dfac8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -192,7 +192,6 @@
private final WindowContext mContext;
private final FeatureFlags mFlags;
private final ScreenshotViewProxy mViewProxy;
- private final ScreenshotActionsProvider.Factory mActionsProviderFactory;
private final ScreenshotNotificationsController mNotificationsController;
private final ScreenshotSmartActions mScreenshotSmartActions;
private final UiEventLogger mUiEventLogger;
@@ -202,7 +201,7 @@
private final ExecutorService mBgExecutor;
private final BroadcastSender mBroadcastSender;
private final BroadcastDispatcher mBroadcastDispatcher;
- private final ActionExecutor mActionExecutor;
+ private final ScreenshotActionsController mActionsController;
private final WindowManager mWindowManager;
private final WindowManager.LayoutParams mWindowLayoutParams;
@@ -217,16 +216,18 @@
private final ActionIntentExecutor mActionIntentExecutor;
private final UserManager mUserManager;
private final AssistContentRequester mAssistContentRequester;
+ private final ActionExecutor mActionExecutor;
+
private final MessageContainerController mMessageContainerController;
private final AnnouncementResolver mAnnouncementResolver;
private Bitmap mScreenBitmap;
private SaveImageInBackgroundTask mSaveInBgTask;
private boolean mScreenshotTakenInPortrait;
- private boolean mBlockAttach;
+ private boolean mAttachRequested;
+ private boolean mDetachRequested;
private Animator mScreenshotAnimation;
private RequestCallback mCurrentRequestCallback;
- private ScreenshotActionsProvider mActionsProvider;
private String mPackageName = "";
private final BroadcastReceiver mCopyBroadcastReceiver;
@@ -253,7 +254,6 @@
WindowManager windowManager,
FeatureFlags flags,
ScreenshotViewProxy.Factory viewProxyFactory,
- ScreenshotActionsProvider.Factory actionsProviderFactory,
ScreenshotSmartActions screenshotSmartActions,
ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
UiEventLogger uiEventLogger,
@@ -265,6 +265,7 @@
BroadcastSender broadcastSender,
BroadcastDispatcher broadcastDispatcher,
ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
+ ScreenshotActionsController.Factory screenshotActionsControllerFactory,
ActionIntentExecutor actionIntentExecutor,
ActionExecutor.Factory actionExecutorFactory,
UserManager userManager,
@@ -276,7 +277,6 @@
@Assisted boolean showUIOnExternalDisplay
) {
mScreenshotSmartActions = screenshotSmartActions;
- mActionsProviderFactory = actionsProviderFactory;
mNotificationsController = screenshotNotificationsControllerFactory.create(
display.getDisplayId());
mUiEventLogger = uiEventLogger;
@@ -327,6 +327,8 @@
finishDismiss();
return Unit.INSTANCE;
});
+ mActionsController = screenshotActionsControllerFactory.getController(mActionExecutor);
+
// Sound is only reproduced from the controller of the default display.
if (mDisplay.getDisplayId() == Display.DEFAULT_DISPLAY) {
@@ -403,20 +405,21 @@
return;
}
+ final UUID requestId;
if (screenshotShelfUi2()) {
- final UUID requestId = UUID.randomUUID();
- final String screenshotId = String.format("Screenshot_%s", requestId);
- mActionsProvider = mActionsProviderFactory.create(
- screenshot, screenshotId, mActionExecutor);
+ requestId = mActionsController.setCurrentScreenshot(screenshot);
saveScreenshotInBackground(screenshot, requestId, finisher);
if (screenshot.getTaskId() >= 0) {
- mAssistContentRequester.requestAssistContent(screenshot.getTaskId(),
- assistContent -> mActionsProvider.onAssistContent(assistContent));
+ mAssistContentRequester.requestAssistContent(
+ screenshot.getTaskId(),
+ assistContent ->
+ mActionsController.onAssistContent(requestId, assistContent));
} else {
- mActionsProvider.onAssistContent(null);
+ mActionsController.onAssistContent(requestId, null);
}
} else {
+ requestId = UUID.randomUUID(); // passed through but unused for legacy UI
saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher,
this::showUiOnActionsReady, this::showUiOnQuickShareActionReady);
}
@@ -425,7 +428,7 @@
setWindowFocusable(true);
mViewProxy.requestFocus();
- enqueueScrollCaptureRequest(screenshot.getUserHandle());
+ enqueueScrollCaptureRequest(requestId, screenshot.getUserHandle());
attachWindow();
@@ -586,11 +589,11 @@
mWindow.setContentView(mViewProxy.getView());
}
- private void enqueueScrollCaptureRequest(UserHandle owner) {
+ private void enqueueScrollCaptureRequest(UUID requestId, UserHandle owner) {
// Wait until this window is attached to request because it is
// the reference used to locate the target window (below).
withWindowAttached(() -> {
- requestScrollCapture(owner);
+ requestScrollCapture(requestId, owner);
mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
new ViewRootImpl.ActivityConfigCallback() {
@Override
@@ -600,14 +603,14 @@
// Hide the scroll chip until we know it's available in this
// orientation
if (screenshotShelfUi2()) {
- mActionsProvider.onScrollChipInvalidated();
+ mActionsController.onScrollChipInvalidated();
} else {
mViewProxy.hideScrollChip();
}
// Delay scroll capture eval a bit to allow the underlying activity
// to set up in the new orientation.
mScreenshotHandler.postDelayed(
- () -> requestScrollCapture(owner), 150);
+ () -> requestScrollCapture(requestId, owner), 150);
mViewProxy.updateInsets(
mWindowManager.getCurrentWindowMetrics().getWindowInsets());
// Screenshot animation calculations won't be valid anymore,
@@ -629,7 +632,7 @@
});
}
- private void requestScrollCapture(UserHandle owner) {
+ private void requestScrollCapture(UUID requestId, UserHandle owner) {
mScrollCaptureExecutor.requestScrollCapture(
mDisplay.getDisplayId(),
mWindow.getDecorView().getWindowToken(),
@@ -637,10 +640,8 @@
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION,
0, response.getPackageName());
if (screenshotShelfUi2()) {
- if (mActionsProvider != null) {
- mActionsProvider.onScrollChipReady(
- () -> onScrollButtonClicked(owner, response));
- }
+ mActionsController.onScrollChipReady(requestId,
+ () -> onScrollButtonClicked(owner, response));
} else {
mViewProxy.showScrollChip(response.getPackageName(),
() -> onScrollButtonClicked(owner, response));
@@ -672,7 +673,7 @@
() -> {
final Intent intent = ActionIntentCreator.INSTANCE.createLongScreenshotIntent(
owner, mContext);
- mActionIntentExecutor.launchIntentAsync(intent, owner, true, null, null);
+ mContext.startActivity(intent);
},
mViewProxy::restoreNonScrollingUi,
mViewProxy::startLongScreenshotTransition);
@@ -687,7 +688,7 @@
new ViewTreeObserver.OnWindowAttachListener() {
@Override
public void onWindowAttached() {
- mBlockAttach = false;
+ mAttachRequested = false;
decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
action.run();
}
@@ -703,13 +704,13 @@
@MainThread
private void attachWindow() {
View decorView = mWindow.getDecorView();
- if (decorView.isAttachedToWindow() || mBlockAttach) {
+ if (decorView.isAttachedToWindow() || mAttachRequested) {
return;
}
if (DEBUG_WINDOW) {
Log.d(TAG, "attachWindow");
}
- mBlockAttach = true;
+ mAttachRequested = true;
mWindowManager.addView(decorView, mWindowLayoutParams);
decorView.requestApplyInsets();
@@ -727,6 +728,11 @@
Log.d(TAG, "Removing screenshot window");
}
mWindowManager.removeViewImmediate(decorView);
+ mDetachRequested = false;
+ }
+ if (mAttachRequested && !mDetachRequested) {
+ mDetachRequested = true;
+ withWindowAttached(this::removeWindow);
}
mViewProxy.stopInputListening();
@@ -826,6 +832,7 @@
/** Reset screenshot view and then call onCompleteRunnable */
private void finishDismiss() {
Log.d(TAG, "finishDismiss");
+ mActionsController.endScreenshotSession();
mScrollCaptureExecutor.close();
if (mCurrentRequestCallback != null) {
mCurrentRequestCallback.onFinish();
@@ -846,9 +853,8 @@
ImageExporter.Result result = future.get();
Log.d(TAG, "Saved screenshot: " + result);
logScreenshotResultStatus(result.uri, screenshot.getUserHandle());
- mScreenshotHandler.resetTimeout();
if (result.uri != null) {
- mActionsProvider.setCompletedScreenshot(new ScreenshotSavedResult(
+ mActionsController.setCompletedScreenshot(requestId, new ScreenshotSavedResult(
result.uri, screenshot.getUserOrDefault(), result.timestamp));
}
if (DEBUG_CALLBACK) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
index 3ac070a..1b5fa34 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
@@ -22,8 +22,13 @@
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Rect
+import android.graphics.Region
+import android.os.Looper
+import android.view.Choreographer
+import android.view.InputEvent
import android.view.KeyEvent
import android.view.LayoutInflater
+import android.view.MotionEvent
import android.view.ScrollCaptureResponse
import android.view.View
import android.view.ViewTreeObserver
@@ -48,6 +53,8 @@
import com.android.systemui.screenshot.ui.binder.ScreenshotShelfViewBinder
import com.android.systemui.screenshot.ui.viewmodel.AnimationState
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
+import com.android.systemui.shared.system.InputChannelCompat
+import com.android.systemui.shared.system.InputMonitorCompat
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -91,6 +98,8 @@
override var isPendingSharedTransition = false
private val animationController = ScreenshotAnimationController(view, viewModel)
+ private var inputMonitor: InputMonitorCompat? = null
+ private var inputEventReceiver: InputChannelCompat.InputEventReceiver? = null
init {
shelfViewBinder.bind(
@@ -106,20 +115,25 @@
setOnKeyListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
debugLog(DEBUG_WINDOW) { "adding OnComputeInternalInsetsListener" }
view.viewTreeObserver.addOnComputeInternalInsetsListener { info ->
- val touchableRegion =
- view.getTouchRegion(
- windowManager.currentWindowMetrics.windowInsets.getInsets(
- WindowInsets.Type.systemGestures()
- )
- )
info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION)
- info.touchableRegion.set(touchableRegion)
+ info.touchableRegion.set(getTouchRegion())
}
screenshotPreview = view.screenshotPreview
thumbnailObserver.setViews(
view.blurredScreenshotPreview,
view.requireViewById(R.id.screenshot_preview_border)
)
+ view.addOnAttachStateChangeListener(
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View) {
+ startInputListening()
+ }
+
+ override fun onViewDetachedFromWindow(v: View) {
+ stopInputListening()
+ }
+ }
+ )
}
override fun reset() {
@@ -236,7 +250,12 @@
callbacks?.onUserInteraction() // reset the timeout
}
- override fun stopInputListening() {}
+ override fun stopInputListening() {
+ inputMonitor?.dispose()
+ inputMonitor = null
+ inputEventReceiver?.dispose()
+ inputEventReceiver = null
+ }
override fun requestFocus() {
view.requestFocus()
@@ -303,6 +322,32 @@
)
}
+ private fun startInputListening() {
+ stopInputListening()
+ inputMonitor =
+ InputMonitorCompat("Screenshot", displayId).also {
+ inputEventReceiver =
+ it.getInputReceiver(Looper.getMainLooper(), Choreographer.getInstance()) {
+ ev: InputEvent? ->
+ if (
+ ev is MotionEvent &&
+ ev.actionMasked == MotionEvent.ACTION_DOWN &&
+ !getTouchRegion().contains(ev.rawX.toInt(), ev.rawY.toInt())
+ ) {
+ callbacks?.onTouchOutside()
+ }
+ }
+ }
+ }
+
+ private fun getTouchRegion(): Region {
+ return view.getTouchRegion(
+ windowManager.currentWindowMetrics.windowInsets.getInsets(
+ WindowInsets.Type.systemGestures()
+ )
+ )
+ }
+
@AssistedFactory
interface Factory : ScreenshotViewProxy.Factory {
override fun getProxy(context: Context, displayId: Int): ScreenshotShelfViewProxy
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index a0c9391..da2024b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -17,9 +17,12 @@
package com.android.systemui.shade
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
import com.android.systemui.plugins.qs.QSContainerController
import com.android.systemui.qs.ui.adapter.QSSceneAdapterImpl
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.carrier.ShadeCarrierGroupControllerLog
import com.android.systemui.shade.data.repository.PrivacyChipRepository
import com.android.systemui.shade.data.repository.PrivacyChipRepositoryImpl
import com.android.systemui.shade.data.repository.ShadeRepository
@@ -143,6 +146,13 @@
fun providesQSContainerController(impl: QSSceneAdapterImpl): QSContainerController {
return impl
}
+
+ @Provides
+ @SysUISingleton
+ @ShadeCarrierGroupControllerLog
+ fun provideShadeCarrierLog(factory: LogBufferFactory): LogBuffer {
+ return factory.create("ShadeCarrierGroupControllerLog", 400)
+ }
}
@Binds
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 4b6dd8d..5e4b173 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
@@ -97,6 +97,8 @@
private final SlotIndexResolver mSlotIndexResolver;
+ private final ShadeCarrierGroupControllerLogger mLogger;
+
private final SignalCallback mSignalCallback = new SignalCallback() {
@Override
public void setMobileDataIndicators(@NonNull MobileDataIndicators indicators) {
@@ -148,6 +150,7 @@
ActivityStarter activityStarter,
@Background Handler bgHandler,
@Main Looper mainLooper,
+ ShadeCarrierGroupControllerLogger logger,
NetworkController networkController,
CarrierTextManager.Builder carrierTextManagerBuilder,
Context context,
@@ -160,6 +163,7 @@
mContext = context;
mActivityStarter = activityStarter;
mBgHandler = bgHandler;
+ mLogger = logger;
mNetworkController = networkController;
mStatusBarPipelineFlags = statusBarPipelineFlags;
mCarrierTextManager = carrierTextManagerBuilder
@@ -374,10 +378,13 @@
return;
}
+ mLogger.logHandleUpdateCarrierInfo(info);
+
mNoSimTextView.setVisibility(View.GONE);
if (!info.airplaneMode && info.anySimReady) {
boolean[] slotSeen = new boolean[SIM_SLOTS];
if (info.listOfCarriers.length == info.subscriptionIds.length) {
+ mLogger.logUsingSimViews();
for (int i = 0; i < SIM_SLOTS && i < info.listOfCarriers.length; i++) {
int slot = getSlotIndex(info.subscriptionIds[i]);
if (slot >= SIM_SLOTS) {
@@ -405,9 +412,11 @@
}
}
} else {
- Log.e(TAG, "Carrier information arrays not of same length");
+ mLogger.logInvalidArrayLengths(
+ info.listOfCarriers.length, info.subscriptionIds.length);
}
} else {
+ 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++) {
@@ -458,6 +467,7 @@
private final ActivityStarter mActivityStarter;
private final Handler mHandler;
private final Looper mLooper;
+ private final ShadeCarrierGroupControllerLogger mLogger;
private final NetworkController mNetworkController;
private final CarrierTextManager.Builder mCarrierTextControllerBuilder;
private final Context mContext;
@@ -472,6 +482,7 @@
ActivityStarter activityStarter,
@Background Handler handler,
@Main Looper looper,
+ ShadeCarrierGroupControllerLogger logger,
NetworkController networkController,
CarrierTextManager.Builder carrierTextControllerBuilder,
Context context,
@@ -484,6 +495,7 @@
mActivityStarter = activityStarter;
mHandler = handler;
mLooper = looper;
+ mLogger = logger;
mNetworkController = networkController;
mCarrierTextControllerBuilder = carrierTextControllerBuilder;
mContext = context;
@@ -505,6 +517,7 @@
mActivityStarter,
mHandler,
mLooper,
+ mLogger,
mNetworkController,
mCarrierTextControllerBuilder,
mContext,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerLog.kt b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerLog.kt
new file mode 100644
index 0000000..36aa7a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.carrier
+
+import javax.inject.Qualifier
+
+/** A [LogBuffer] for detailed carrier text logs for [ShadeCarrierGroupController]. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class ShadeCarrierGroupControllerLog
diff --git a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerLogger.kt
new file mode 100644
index 0000000..af06a35
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerLogger.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.shade.carrier
+
+import com.android.keyguard.CarrierTextManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import javax.inject.Inject
+
+/** Logger for [ShadeCarrierGroupController], mostly to try and solve b/341841138. */
+@SysUISingleton
+class ShadeCarrierGroupControllerLogger
+@Inject
+constructor(@ShadeCarrierGroupControllerLog val buffer: LogBuffer) {
+ /** De-structures the info object so that we don't have to generate new strings */
+ fun logHandleUpdateCarrierInfo(info: CarrierTextManager.CarrierTextCallbackInfo) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = "${info.carrierText}"
+ bool1 = info.anySimReady
+ bool2 = info.airplaneMode
+ },
+ {
+ "handleUpdateCarrierInfo: " +
+ "result=(carrierText=$str1, anySimReady=$bool1, airplaneMode=$bool2)"
+ },
+ )
+ }
+
+ fun logInvalidArrayLengths(numCarriers: Int, numSubs: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.ERROR,
+ {
+ int1 = numCarriers
+ int2 = numSubs
+ },
+ { "â”— carriers.length != subIds.length. carriers.length=$int1 subs.length=$int2" },
+ )
+ }
+
+ fun logUsingNoSimView(text: CharSequence) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ { str1 = "$text" },
+ { "â”— updating No SIM view with text=$str1" },
+ )
+ }
+
+ fun logUsingSimViews() {
+ buffer.log(TAG, LogLevel.VERBOSE, {}, { "â”— updating SIM views" })
+ }
+
+ private companion object {
+ const val TAG = "SCGC"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FlingInfo.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FlingInfo.kt
index d7f96e6..ea2f9ab 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FlingInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FlingInfo.kt
@@ -16,11 +16,16 @@
package com.android.systemui.shade.data.repository
+import java.util.UUID
+
/**
* Information about a fling on the shade: whether we're flinging expanded or collapsed, and the
* velocity of the touch gesture that started the fling (if applicable).
*/
-data class FlingInfo(
+data class FlingInfo @JvmOverloads constructor(
val expand: Boolean,
val velocity: Float = 0f,
+
+ /** Required to emit duplicate FlingInfo from StateFlow. */
+ val id: UUID = UUID.randomUUID()
)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index 53c10a3..fe16fc0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -49,11 +49,13 @@
sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
shadeRepository: ShadeRepository,
) : BaseShadeInteractor {
+ override val shadeMode: StateFlow<ShadeMode> = shadeRepository.shadeMode
+
override val shadeExpansion: StateFlow<Float> =
- sceneBasedExpansion(sceneInteractor, Scenes.Shade)
+ sceneBasedExpansion(sceneInteractor, notificationsScene)
.stateIn(scope, SharingStarted.Eagerly, 0f)
- private val sceneBasedQsExpansion = sceneBasedExpansion(sceneInteractor, Scenes.QuickSettings)
+ private val sceneBasedQsExpansion = sceneBasedExpansion(sceneInteractor, quickSettingsScene)
override val qsExpansion: StateFlow<Float> =
combine(
@@ -81,7 +83,7 @@
when (state) {
is ObservableTransitionState.Idle -> false
is ObservableTransitionState.Transition ->
- state.toScene == Scenes.QuickSettings && state.fromScene != Scenes.Shade
+ state.toScene == quickSettingsScene && state.fromScene != notificationsScene
}
}
.distinctUntilChanged()
@@ -90,7 +92,7 @@
sceneInteractor.transitionState
.map { state ->
when (state) {
- is ObservableTransitionState.Idle -> state.currentScene == Scenes.QuickSettings
+ is ObservableTransitionState.Idle -> state.currentScene == quickSettingsScene
is ObservableTransitionState.Transition -> false
}
}
@@ -106,12 +108,10 @@
.stateIn(scope, SharingStarted.Eagerly, false)
override val isUserInteractingWithShade: Flow<Boolean> =
- sceneBasedInteracting(sceneInteractor, Scenes.Shade)
+ sceneBasedInteracting(sceneInteractor, notificationsScene)
override val isUserInteractingWithQs: Flow<Boolean> =
- sceneBasedInteracting(sceneInteractor, Scenes.QuickSettings)
-
- override val shadeMode: StateFlow<ShadeMode> = shadeRepository.shadeMode
+ sceneBasedInteracting(sceneInteractor, quickSettingsScene)
/**
* Returns a flow that uses scene transition progress to and from a scene that is pulled down
@@ -154,4 +154,20 @@
}
}
.distinctUntilChanged()
+
+ private val notificationsScene: SceneKey
+ get() =
+ if (shadeMode.value is ShadeMode.Dual) {
+ Scenes.NotificationsShade
+ } else {
+ Scenes.Shade
+ }
+
+ private val quickSettingsScene: SceneKey
+ get() =
+ if (shadeMode.value is ShadeMode.Dual) {
+ Scenes.QuickSettingsShade
+ } else {
+ Scenes.QuickSettings
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 8d8a36a..47939ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -98,6 +98,8 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.deviceentry.domain.interactor.BiometricMessageInteractor;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor;
import com.android.systemui.dock.DockManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardIndication;
@@ -183,11 +185,14 @@
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private KeyguardInteractor mKeyguardInteractor;
private final BiometricMessageInteractor mBiometricMessageInteractor;
+ private DeviceEntryFingerprintAuthInteractor mDeviceEntryFingerprintAuthInteractor;
+ private DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
private String mPersistentUnlockMessage;
private String mAlignmentIndication;
private boolean mForceIsDismissible;
private CharSequence mTrustGrantedIndication;
private CharSequence mTransientIndication;
+ private CharSequence mTrustAgentErrorMessage;
private CharSequence mBiometricMessage;
private CharSequence mBiometricMessageFollowUp;
private BiometricSourceType mBiometricMessageSource;
@@ -235,6 +240,13 @@
final Consumer<Set<Integer>> mCoExAcquisitionMsgIdsToShowCallback =
(Set<Integer> coExFaceAcquisitionMsgIdsToShow) -> mCoExFaceAcquisitionMsgIdsToShow =
coExFaceAcquisitionMsgIdsToShow;
+ @VisibleForTesting
+ final Consumer<Boolean> mIsFingerprintEngagedCallback =
+ (Boolean isEngaged) -> {
+ if (!isEngaged) {
+ showTrustAgentErrorMessage(mTrustAgentErrorMessage);
+ }
+ };
private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
@Override
public void onScreenTurnedOn() {
@@ -295,7 +307,9 @@
FeatureFlags flags,
IndicationHelper indicationHelper,
KeyguardInteractor keyguardInteractor,
- BiometricMessageInteractor biometricMessageInteractor
+ BiometricMessageInteractor biometricMessageInteractor,
+ DeviceEntryFingerprintAuthInteractor deviceEntryFingerprintAuthInteractor,
+ DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor
) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
@@ -325,6 +339,8 @@
mIndicationHelper = indicationHelper;
mKeyguardInteractor = keyguardInteractor;
mBiometricMessageInteractor = biometricMessageInteractor;
+ mDeviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor;
+ mDeviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor;
mFaceAcquiredMessageDeferral = faceHelpMessageDeferral.create();
@@ -409,6 +425,8 @@
collectFlow(mIndicationArea,
mBiometricMessageInteractor.getCoExFaceAcquisitionMsgIdsToShow(),
mCoExAcquisitionMsgIdsToShowCallback);
+ collectFlow(mIndicationArea, mDeviceEntryFingerprintAuthInteractor.isEngaged(),
+ mIsFingerprintEngagedCallback);
}
/**
@@ -944,19 +962,25 @@
if (!isSuccessMessage
&& mBiometricMessageSource == FINGERPRINT
- && biometricSourceType != FINGERPRINT) {
- // drop all non-fingerprint biometric messages if there's a fingerprint message showing
- mKeyguardLogger.logDropNonFingerprintMessage(
+ && biometricSourceType == FACE) {
+ // drop any face messages if there's a fingerprint message showing
+ mKeyguardLogger.logDropFaceMessage(
biometricMessage,
- biometricMessageFollowUp,
- biometricSourceType
+ biometricMessageFollowUp
);
return;
}
- mBiometricMessage = biometricMessage;
- mBiometricMessageFollowUp = biometricMessageFollowUp;
- mBiometricMessageSource = biometricSourceType;
+ if (mBiometricMessageSource != null && biometricSourceType == null) {
+ // If there's a current biometric message showing and a non-biometric message
+ // arrives, update the followup message with the non-biometric message.
+ // Keep the biometricMessage and biometricMessageSource the same.
+ mBiometricMessageFollowUp = biometricMessage;
+ } else {
+ mBiometricMessage = biometricMessage;
+ mBiometricMessageFollowUp = biometricMessageFollowUp;
+ mBiometricMessageSource = biometricSourceType;
+ }
mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK);
hideBiometricMessageDelayed(
@@ -1455,7 +1479,7 @@
@Override
public void onTrustAgentErrorMessage(CharSequence message) {
- showBiometricMessage(message, null);
+ showTrustAgentErrorMessage(message);
}
@Override
@@ -1467,6 +1491,10 @@
hideBiometricMessage();
mBiometricErrorMessageToShowOnScreenOn = null;
}
+
+ if (!running && biometricSourceType == FACE) {
+ showTrustAgentErrorMessage(mTrustAgentErrorMessage);
+ }
}
@Override
@@ -1533,6 +1561,25 @@
return getCurrentUser() == userId;
}
+ /**
+ * Only show trust agent messages after biometrics are no longer active.
+ */
+ private void showTrustAgentErrorMessage(CharSequence message) {
+ if (message == null) {
+ mTrustAgentErrorMessage = null;
+ return;
+ }
+ boolean fpEngaged = mDeviceEntryFingerprintAuthInteractor.isEngaged().getValue();
+ boolean faceRunning = mDeviceEntryFaceAuthInteractor.isRunning();
+ if (fpEngaged || faceRunning) {
+ mKeyguardLogger.delayShowingTrustAgentError(message, fpEngaged, faceRunning);
+ mTrustAgentErrorMessage = message;
+ } else {
+ mTrustAgentErrorMessage = null;
+ showBiometricMessage(message, null);
+ }
+ }
+
protected void showTrustGrantedMessage(boolean dismissKeyguard, @Nullable String message) {
mTrustGrantedIndication = message;
updateDeviceEntryIndication(false);
@@ -1639,6 +1686,7 @@
new KeyguardStateController.Callback() {
@Override
public void onUnlockedChanged() {
+ mTrustAgentErrorMessage = null;
updateDeviceEntryIndication(false);
}
@@ -1649,6 +1697,7 @@
mKeyguardLogger.log(TAG, LogLevel.DEBUG, "clear messages");
mTopIndicationView.clearMessages();
mRotateTextViewController.clearMessages();
+ mTrustAgentErrorMessage = null;
} else {
updateDeviceEntryIndication(false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 5bf2f41..03c6670 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -15,7 +15,7 @@
*/
package com.android.systemui.statusbar;
-import static com.android.systemui.Flags.mediaControlsUserInitiatedDismiss;
+import static com.android.systemui.Flags.mediaControlsUserInitiatedDeleteintent;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -178,7 +178,7 @@
@Override
public void onMediaDataRemoved(@NonNull String key, boolean userInitiated) {
- if (mediaControlsUserInitiatedDismiss() && !userInitiated) {
+ if (mediaControlsUserInitiatedDeleteintent() && !userInitiated) {
// Dismissing the notification will send the app's deleteIntent, so ignore if
// this was an automatic removal
Log.d(TAG, "Not dismissing " + key + " because it was removed by the system");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
index e85df0e..c57cf69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
@@ -18,7 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.chips.domain.interactor.OngoingActivityChipInteractor
-import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.domain.model.OngoingActivityChipModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/domain/interactor/OngoingActivityChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/domain/interactor/OngoingActivityChipInteractor.kt
index 70362c8..c3d37fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/domain/interactor/OngoingActivityChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/domain/interactor/OngoingActivityChipInteractor.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.chips.domain.interactor
-import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.domain.model.OngoingActivityChipModel
import kotlinx.coroutines.flow.StateFlow
/** Interface for an interactor that knows the state of a single type of ongoing activity chip. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/domain/model/OngoingActivityChipModel.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/chips/domain/model/OngoingActivityChipModel.kt
index e63713b..c753918 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/domain/model/OngoingActivityChipModel.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.chips.ui.model
+package com.android.systemui.statusbar.chips.domain.model
import android.view.View
import com.android.systemui.common.shared.model.Icon
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
index 6f16969..bff5686 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
@@ -16,17 +16,51 @@
package com.android.systemui.statusbar.chips.screenrecord.domain.interactor
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import com.android.systemui.screenrecord.data.model.ScreenRecordModel
+import com.android.systemui.screenrecord.data.repository.ScreenRecordRepository
import com.android.systemui.statusbar.chips.domain.interactor.OngoingActivityChipInteractor
+import com.android.systemui.statusbar.chips.domain.model.OngoingActivityChipModel
+import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/** Interactor for the screen recording chip shown in the status bar. */
@SysUISingleton
-open class ScreenRecordChipInteractor @Inject constructor() : OngoingActivityChipInteractor {
- // TODO(b/332662551): Implement this flow.
+open class ScreenRecordChipInteractor
+@Inject
+constructor(
+ @Application scope: CoroutineScope,
+ screenRecordRepository: ScreenRecordRepository,
+ val systemClock: SystemClock,
+) : OngoingActivityChipInteractor {
override val chip: StateFlow<OngoingActivityChipModel> =
- MutableStateFlow(OngoingActivityChipModel.Hidden)
+ screenRecordRepository.screenRecordState
+ .map { state ->
+ when (state) {
+ is ScreenRecordModel.DoingNothing,
+ // TODO(b/332662551): Implement the 3-2-1 countdown chip.
+ is ScreenRecordModel.Starting -> OngoingActivityChipModel.Hidden
+ is ScreenRecordModel.Recording ->
+ OngoingActivityChipModel.Shown(
+ // TODO(b/332662551): Also provide a content description.
+ icon =
+ Icon.Resource(
+ R.drawable.stat_sys_screen_record,
+ contentDescription = null
+ ),
+ startTimeMs = systemClock.elapsedRealtime()
+ ) {
+ // TODO(b/332662551): Implement the pause dialog.
+ }
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
index 47b2b03..208eb50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
@@ -19,8 +19,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInteractor
+import com.android.systemui.statusbar.chips.domain.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor
-import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt
index 8505c5f..0d5ade7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt
@@ -98,10 +98,10 @@
return
}
val r = mContext.resources
- val defaultThreshold = r.getDimensionPixelSize(R.dimen.system_gestures_start_threshold)
- mSwipeStartThreshold[defaultThreshold, defaultThreshold, defaultThreshold] =
- defaultThreshold
- mSwipeDistanceThreshold = defaultThreshold
+ val startThreshold = r.getDimensionPixelSize(R.dimen.system_gestures_start_threshold)
+ mSwipeStartThreshold[startThreshold, startThreshold, startThreshold] = startThreshold
+ mSwipeDistanceThreshold =
+ r.getDimensionPixelSize(R.dimen.system_gestures_distance_threshold)
val display = DisplayManagerGlobal.getInstance().getRealDisplay(mContext.displayId)
val displayCutout = display.cutout
if (displayCutout != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt
index 2fd0a53..0ece88d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt
@@ -46,7 +46,7 @@
private var monitoringCurrentTouch: Boolean = false
private var swipeDistanceThreshold: Int = context.resources.getDimensionPixelSize(
- com.android.internal.R.dimen.system_gestures_start_threshold
+ com.android.internal.R.dimen.system_gestures_distance_threshold
)
override fun onInputEvent(ev: InputEvent) {
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 87f11f13..938a71f 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
@@ -48,6 +48,9 @@
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.SystemSettings
import com.android.systemui.util.time.SystemClock
+import com.android.wm.shell.bubbles.Bubbles
+import java.util.Optional
+import kotlin.jvm.optionals.getOrElse
class PeekDisabledSuppressor(
private val globalSettings: GlobalSettings,
@@ -119,12 +122,18 @@
}
}
-class PeekAlreadyBubbledSuppressor(private val statusBarStateController: StatusBarStateController) :
- VisualInterruptionFilter(types = setOf(PEEK), reason = "already bubbled") {
+class PeekAlreadyBubbledSuppressor(
+ private val statusBarStateController: StatusBarStateController,
+ private val bubbles: Optional<Bubbles>
+) : VisualInterruptionFilter(types = setOf(PEEK), reason = "already bubbled") {
override fun shouldSuppress(entry: NotificationEntry) =
when {
statusBarStateController.state != SHADE -> false
- else -> entry.isBubble
+ else -> {
+ val bubblesCanShowNotification =
+ bubbles.map { it.canShowBubbleNotification() }.getOrElse { false }
+ entry.isBubble && bubblesCanShowNotification
+ }
}
}
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 9c6a423..74925c8 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
@@ -52,9 +52,11 @@
import com.android.systemui.util.EventLog;
import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.time.SystemClock;
+import com.android.wm.shell.bubbles.Bubbles;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import javax.inject.Inject;
@@ -83,6 +85,7 @@
private final SystemClock mSystemClock;
private final GlobalSettings mGlobalSettings;
private final EventLog mEventLog;
+ private final Optional<Bubbles> mBubbles;
@VisibleForTesting
protected boolean mUseHeadsUp = false;
@@ -132,7 +135,8 @@
DeviceProvisionedController deviceProvisionedController,
SystemClock systemClock,
GlobalSettings globalSettings,
- EventLog eventLog) {
+ EventLog eventLog,
+ Optional<Bubbles> bubbles) {
mPowerManager = powerManager;
mBatteryController = batteryController;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
@@ -148,6 +152,7 @@
mSystemClock = systemClock;
mGlobalSettings = globalSettings;
mEventLog = eventLog;
+ mBubbles = bubbles;
ContentObserver headsUpObserver = new ContentObserver(mainHandler) {
@Override
public void onChange(boolean selfChange) {
@@ -440,7 +445,9 @@
}
boolean inShade = mStatusBarStateController.getState() == SHADE;
- if (entry.isBubble() && inShade) {
+ boolean bubblesCanShowNotification =
+ mBubbles.isPresent() && mBubbles.get().canShowBubbleNotification();
+ if (entry.isBubble() && inShade && bubblesCanShowNotification) {
if (log) mLogger.logNoHeadsUpAlreadyBubbled(entry);
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index f68e194..7e16cd5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -43,6 +43,8 @@
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.SystemSettings
import com.android.systemui.util.time.SystemClock
+import com.android.wm.shell.bubbles.Bubbles
+import java.util.Optional
import javax.inject.Inject
class VisualInterruptionDecisionProviderImpl
@@ -65,7 +67,8 @@
private val userTracker: UserTracker,
private val avalancheProvider: AvalancheProvider,
private val systemSettings: SystemSettings,
- private val packageManager: PackageManager
+ private val packageManager: PackageManager,
+ private val bubbles: Optional<Bubbles>
) : VisualInterruptionDecisionProvider {
init {
@@ -158,7 +161,7 @@
addCondition(PulseDisabledSuppressor(ambientDisplayConfiguration, userTracker))
addCondition(PulseBatterySaverSuppressor(batteryController))
addFilter(PeekPackageSnoozedSuppressor(headsUpManager))
- addFilter(PeekAlreadyBubbledSuppressor(statusBarStateController))
+ addFilter(PeekAlreadyBubbledSuppressor(statusBarStateController, bubbles))
addFilter(PeekDndSuppressor())
addFilter(PeekNotImportantSuppressor())
addCondition(PeekDeviceNotInUseSuppressor(powerManager, statusBarStateController))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 6137381..c2ce114 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -18,6 +18,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -25,6 +26,7 @@
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimClipping
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
@@ -34,6 +36,7 @@
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
@@ -73,16 +76,16 @@
}
is ObservableTransitionState.Transition -> {
if (
- (transitionState.fromScene == Scenes.Shade &&
- transitionState.toScene == Scenes.QuickSettings) ||
- (transitionState.fromScene == Scenes.QuickSettings &&
- transitionState.toScene == Scenes.Shade)
+ (transitionState.fromScene == notificationsScene &&
+ transitionState.toScene == quickSettingsScene) ||
+ (transitionState.fromScene == quickSettingsScene &&
+ transitionState.toScene == notificationsScene)
) {
1f
} else if (
(transitionState.fromScene == Scenes.Gone ||
transitionState.fromScene == Scenes.Lockscreen) &&
- transitionState.toScene == Scenes.QuickSettings
+ transitionState.toScene == quickSettingsScene
) {
// during QS expansion, increase fraction at same rate as scrim alpha,
// but start when scrim alpha is at EXPANSION_FOR_DELAYED_STACK_FADE_IN.
@@ -152,7 +155,9 @@
/** Whether the notification stack is scrollable or not. */
val isScrollable: Flow<Boolean> =
- sceneInteractor.currentScene.map { it == Scenes.Shade }.dumpWhileCollecting("isScrollable")
+ sceneInteractor.currentScene
+ .map { it == notificationsScene }
+ .dumpWhileCollecting("isScrollable")
/** Whether the notification stack is displayed in doze mode. */
val isDozing: Flow<Boolean> by lazy {
@@ -162,4 +167,22 @@
keyguardInteractor.get().isDozing.dumpWhileCollecting("isDozing")
}
}
+
+ private val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
+
+ private val notificationsScene: SceneKey
+ get() =
+ if (shadeMode.value is ShadeMode.Dual) {
+ Scenes.NotificationsShade
+ } else {
+ Scenes.Shade
+ }
+
+ private val quickSettingsScene: SceneKey
+ get() =
+ if (shadeMode.value is ShadeMode.Dual) {
+ Scenes.QuickSettingsShade
+ } else {
+ Scenes.QuickSettings
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DialogDelegate.kt
index 25d1f05..2beb66b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DialogDelegate.kt
@@ -19,7 +19,10 @@
import android.app.Dialog
import android.content.res.Configuration
import android.os.Bundle
+import android.util.DisplayMetrics
import android.view.ViewRootImpl
+import com.android.systemui.animation.back.BackAnimationSpec
+import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi
/**
* A delegate class that should be implemented in place of subclassing [Dialog].
@@ -49,4 +52,7 @@
fun getWidth(dialog: T): Int = SystemUIDialog.getDefaultDialogWidth(dialog)
fun getHeight(dialog: T): Int = SystemUIDialog.getDefaultDialogHeight()
+
+ fun getBackAnimationSpec(displayMetricsProvider: () -> DisplayMetrics): BackAnimationSpec =
+ BackAnimationSpec.floatingSystemSurfacesForSysUi(displayMetricsProvider)
}
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 3063aed..77f3706 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -54,13 +54,13 @@
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.util.Assert;
+import com.android.systemui.util.CopyOnLoopListenerSet;
+import com.android.systemui.util.IListenerSet;
import dagger.Lazy;
import kotlinx.coroutines.ExperimentalCoroutinesApi;
-import java.util.ArrayList;
-
import javax.inject.Inject;
/**
@@ -69,7 +69,7 @@
@ExperimentalCoroutinesApi @SysUISingleton
public final class DozeServiceHost implements DozeHost {
private static final String TAG = "DozeServiceHost";
- private final ArrayList<Callback> mCallbacks = new ArrayList<>();
+ private final IListenerSet<Callback> mCallbacks = new CopyOnLoopListenerSet<>();
private final DozeLog mDozeLog;
private final PowerManager mPowerManager;
private boolean mAnimateWakeup;
@@ -178,8 +178,8 @@
*/
public void fireSideFpsAcquisitionStarted() {
Assert.isMainThread();
- for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).onSideFingerprintAcquisitionStarted();
+ for (Callback callback : mCallbacks) {
+ callback.onSideFingerprintAcquisitionStarted();
}
}
@@ -211,7 +211,7 @@
@Override
public void addCallback(@NonNull Callback callback) {
Assert.isMainThread();
- mCallbacks.add(callback);
+ mCallbacks.addIfAbsent(callback);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 11feb97..f99a81e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -159,7 +159,7 @@
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (shouldBeVisible()) {
- updateTopEntry();
+ updateTopEntry("onLayoutChange");
// trigger scroller to notify the latest panel translation
mStackScrollerController.requestLayout();
@@ -220,7 +220,7 @@
@Override
public void onHeadsUpPinned(NotificationEntry entry) {
- updateTopEntry();
+ updateTopEntry("onHeadsUpPinned");
updateHeader(entry);
updateHeadsUpAndPulsingRoundness(entry);
}
@@ -231,7 +231,7 @@
mPhoneStatusBarTransitions.onHeadsUpStateChanged(isHeadsUp);
}
- private void updateTopEntry() {
+ private void updateTopEntry(String reason) {
NotificationEntry newEntry = null;
if (shouldBeVisible()) {
newEntry = mHeadsUpManager.getTopEntry();
@@ -370,7 +370,7 @@
@Override
public void onHeadsUpUnPinned(NotificationEntry entry) {
- updateTopEntry();
+ updateTopEntry("onHeadsUpUnPinned");
updateHeader(entry);
updateHeadsUpAndPulsingRoundness(entry);
}
@@ -388,7 +388,7 @@
updateHeadsUpHeaders();
}
if (isExpanded() != oldIsExpanded) {
- updateTopEntry();
+ updateTopEntry("setAppearFraction");
}
}
@@ -462,11 +462,11 @@
}
public void onStateChanged() {
- updateTopEntry();
+ updateTopEntry("onStateChanged");
}
@Override
public void onFullyHiddenChanged(boolean isFullyHidden) {
- updateTopEntry();
+ updateTopEntry("onFullyHiddenChanged");
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 68457ea..ffc859e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -249,7 +249,7 @@
for (NotificationEntry entry : mEntriesToRemoveAfterExpand) {
if (isHeadsUpEntry(entry.getKey())) {
// Maybe the heads-up was removed already
- removeEntry(entry.getKey());
+ removeEntry(entry.getKey(), "onExpandingFinished");
}
}
}
@@ -381,7 +381,7 @@
for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
if (isHeadsUpEntry(entry.getKey())) {
// Maybe the heads-up was removed already
- removeEntry(entry.getKey());
+ removeEntry(entry.getKey(), "mOnReorderingAllowedListener");
}
}
mEntriesToRemoveWhenReorderingAllowed.clear();
@@ -572,7 +572,7 @@
} else if (mTrackingHeadsUp) {
mEntriesToRemoveAfterExpand.add(entry);
} else {
- removeEntry(entry.getKey());
+ removeEntry(entry.getKey(), "createRemoveRunnable");
}
};
}
@@ -661,7 +661,7 @@
}
}
for (String key : keysToRemove) {
- removeEntry(key);
+ removeEntry(key, "mStatusBarStateListener");
}
}
}
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 f767262..6d4301f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -660,10 +660,8 @@
* whether heads up is visible.
*/
public void updateForHeadsUp() {
- if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
- // [KeyguardStatusBarViewBinder] handles visibility changes due to heads up states.
- return;
- }
+ // [KeyguardStatusBarViewBinder] handles visibility when SceneContainerFlag is on.
+ SceneContainerFlag.assertInLegacyMode();
updateForHeadsUp(true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index c74dde5..e01556f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -269,9 +269,12 @@
mOnCreateRunnables.get(i).run();
}
if (predictiveBackAnimateDialogs()) {
+ View targetView = getWindow().getDecorView();
DialogKt.registerAnimationOnBackInvoked(
/* dialog = */ this,
- /* targetView = */ getWindow().getDecorView()
+ /* targetView = */ targetView,
+ /* backAnimationSpec= */mDelegate.getBackAnimationSpec(
+ () -> targetView.getResources().getDisplayMetrics())
);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index 12f252d..9d9cc2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.pipeline.satellite.data.prod
import android.os.OutcomeReceiver
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyManager
import android.telephony.satellite.NtnSignalStrengthCallback
import android.telephony.satellite.SatelliteManager
import android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS
@@ -38,6 +40,7 @@
import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Unknown
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
import com.android.systemui.util.kotlin.getOrNull
+import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.time.SystemClock
import java.util.Optional
import javax.inject.Inject
@@ -51,12 +54,15 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -92,13 +98,19 @@
@OptIn(ExperimentalCoroutinesApi::class)
companion object {
- /** Convenience function to switch to the supported flow */
+ /**
+ * Convenience function to switch to the supported flow. [retrySignal] is a flow that emits
+ * [Unit] whenever the [supported] flow needs to be restarted
+ */
fun <T> Flow<SatelliteSupport>.whenSupported(
supported: (SatelliteManager) -> Flow<T>,
orElse: Flow<T>,
- ): Flow<T> = flatMapLatest {
- when (it) {
- is Supported -> supported(it.satelliteManager)
+ retrySignal: Flow<Unit>,
+ ): Flow<T> = flatMapLatest { satelliteSupport ->
+ when (satelliteSupport) {
+ is Supported -> {
+ retrySignal.flatMapLatest { supported(satelliteSupport.satelliteManager) }
+ }
else -> orElse
}
}
@@ -132,6 +144,7 @@
@Inject
constructor(
satelliteManagerOpt: Optional<SatelliteManager>,
+ telephonyManager: TelephonyManager,
@Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
@OemSatelliteInputLog private val logBuffer: LogBuffer,
@@ -201,11 +214,65 @@
}
}
+ /**
+ * Note that we are given an "unbound" [TelephonyManager] (meaning it was not created with a
+ * specific `subscriptionId`). Therefore this is the radio power state of the
+ * DEFAULT_SUBSCRIPTION_ID subscription. This subscription, I am led to believe, is the one that
+ * would be used for the SatelliteManager subscription.
+ *
+ * By watching power state changes, we can detect if the telephony process crashes.
+ *
+ * See b/337258696 for details
+ */
+ private val radioPowerState: StateFlow<Int> =
+ conflatedCallbackFlow {
+ val cb =
+ object : TelephonyCallback(), TelephonyCallback.RadioPowerStateListener {
+ override fun onRadioPowerStateChanged(powerState: Int) {
+ trySend(powerState)
+ }
+ }
+
+ telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), cb)
+
+ awaitClose { telephonyManager.unregisterTelephonyCallback(cb) }
+ }
+ .flowOn(bgDispatcher)
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ TelephonyManager.RADIO_POWER_UNAVAILABLE
+ )
+
+ /**
+ * In the event that a telephony phone process has crashed, we expect to see a radio power state
+ * change from ON to something else. This trigger can be used to re-start a flow via
+ * [whenSupported]
+ *
+ * This flow emits [Unit] when started so that newly-started collectors always run, and only
+ * restart when the state goes from ON -> !ON
+ */
+ private val telephonyProcessCrashedEvent: Flow<Unit> =
+ radioPowerState
+ .pairwise()
+ .mapNotNull { (prev: Int, new: Int) ->
+ if (
+ prev == TelephonyManager.RADIO_POWER_ON &&
+ new != TelephonyManager.RADIO_POWER_ON
+ ) {
+ Unit
+ } else {
+ null
+ }
+ }
+ .onStart { emit(Unit) }
+
override val connectionState =
satelliteSupport
.whenSupported(
supported = ::connectionStateFlow,
- orElse = flowOf(SatelliteConnectionState.Off)
+ orElse = flowOf(SatelliteConnectionState.Off),
+ retrySignal = telephonyProcessCrashedEvent,
)
.stateIn(scope, SharingStarted.Eagerly, SatelliteConnectionState.Off)
@@ -232,7 +299,11 @@
override val signalStrength =
satelliteSupport
- .whenSupported(supported = ::signalStrengthFlow, orElse = flowOf(0))
+ .whenSupported(
+ supported = ::signalStrengthFlow,
+ orElse = flowOf(0),
+ retrySignal = telephonyProcessCrashedEvent,
+ )
.stateIn(scope, SharingStarted.Eagerly, 0)
// By using the SupportedSatelliteManager here, we expect registration never to fail
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 332c121..d76fd40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -18,6 +18,7 @@
import android.content.Context
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
@@ -39,6 +40,7 @@
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/**
@@ -60,6 +62,7 @@
}
@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
class DeviceBasedSatelliteViewModelImpl
@Inject
constructor(
@@ -124,18 +127,37 @@
shouldActuallyShowIcon,
interactor.connectionState,
) { shouldShow, connectionState ->
+ logBuffer.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ bool1 = shouldShow
+ str1 = connectionState.name
+ },
+ { "Updating carrier text. shouldActuallyShow=$bool1 connectionState=$str1" }
+ )
if (shouldShow) {
when (connectionState) {
SatelliteConnectionState.On,
SatelliteConnectionState.Connected ->
context.getString(R.string.satellite_connected_carrier_text)
SatelliteConnectionState.Off,
- SatelliteConnectionState.Unknown -> null
+ SatelliteConnectionState.Unknown -> {
+ null
+ }
}
} else {
null
}
}
+ .onEach {
+ logBuffer.log(
+ TAG,
+ LogLevel.INFO,
+ { str1 = it },
+ { "Resulting carrier text = $str1" }
+ )
+ }
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index 8b48bd3..2169154 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -79,7 +79,8 @@
runnable.run()
return
}
- val fn = "[$label] => AvalancheController.update [${getKey(entry)}]"
+ log { "\n "}
+ val fn = "$label => AvalancheController.update ${getKey(entry)}"
if (entry == null) {
log { "Entry is NULL, stop update." }
return
@@ -88,13 +89,13 @@
debugRunnableLabelMap[runnable] = label
}
if (isShowing(entry)) {
- log { "\n$fn => [update showing]" }
+ log { "\n$fn => update showing" }
runnable.run()
} else if (entry in nextMap) {
- log { "\n$fn => [update next]" }
+ log { "\n$fn => update next" }
nextMap[entry]?.add(runnable)
} else if (headsUpEntryShowing == null) {
- log { "\n$fn => [showNow]" }
+ log { "\n$fn => showNow" }
showNow(entry, arrayListOf(runnable))
} else {
// Clean up invalid state when entry is in list but not map and vice versa
@@ -133,20 +134,22 @@
runnable.run()
return
}
- val fn = "[$label] => AvalancheController.delete " + getKey(entry)
+ log { "\n "}
+ val fn = "$label => AvalancheController.delete " + getKey(entry)
if (entry == null) {
- log { "$fn => cannot remove NULL entry" }
+ log { "$fn => entry NULL, running runnable" }
+ runnable.run()
return
}
if (entry in nextMap) {
- log { "$fn => [remove from next]" }
+ log { "$fn => remove from next" }
if (entry in nextMap) nextMap.remove(entry)
if (entry in nextList) nextList.remove(entry)
} else if (entry in debugDropSet) {
- log { "$fn => [remove from dropset]" }
+ log { "$fn => remove from dropset" }
debugDropSet.remove(entry)
} else if (isShowing(entry)) {
- log { "$fn => [remove showing ${getKey(entry)}]" }
+ log { "$fn => remove showing ${getKey(entry)}" }
previousHunKey = getKey(headsUpEntryShowing)
// Show the next HUN before removing this one, so that we don't tell listeners
// onHeadsUpPinnedModeChanged, which causes
@@ -155,7 +158,7 @@
showNext()
runnable.run()
} else {
- log { "$fn => [removing untracked ${getKey(entry)}]" }
+ log { "$fn => removing untracked ${getKey(entry)}" }
}
logState("after $fn")
}
@@ -239,6 +242,18 @@
return keyList
}
+ fun getWaitingEntry(key: String): HeadsUpEntry? {
+ if (!NotificationThrottleHun.isEnabled) {
+ return null
+ }
+ for (headsUpEntry in nextMap.keys) {
+ if (headsUpEntry.mEntry?.key.equals(key)) {
+ return headsUpEntry
+ }
+ }
+ return null
+ }
+
private fun isShowing(entry: HeadsUpEntry): Boolean {
return headsUpEntryShowing != null && entry.mEntry?.key == headsUpEntryShowing?.mEntry?.key
}
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 a7fe49b..2ee98bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -195,26 +195,29 @@
*/
@Override
public boolean removeNotification(@NonNull String key, boolean releaseImmediately) {
- mLogger.logRemoveNotification(key, releaseImmediately);
+ final boolean isWaiting = mAvalancheController.isWaiting(key);
+ mLogger.logRemoveNotification(key, releaseImmediately, isWaiting);
if (mAvalancheController.isWaiting(key)) {
- removeEntry(key);
+ removeEntry(key, "removeNotification (isWaiting)");
return true;
}
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
if (headsUpEntry == null) {
return true;
}
- if (releaseImmediately || canRemoveImmediately(key)) {
- removeEntry(key);
- } else {
- headsUpEntry.removeAsSoonAsPossible();
- return false;
+ if (releaseImmediately) {
+ removeEntry(key, "removeNotification (releaseImmediately)");
+ return true;
}
- return true;
+ if (canRemoveImmediately(key)) {
+ removeEntry(key, "removeNotification (canRemoveImmediately)");
+ return true;
+ }
+ headsUpEntry.removeAsSoonAsPossible();
+ return false;
}
-
/**
* Called when the notification state has been updated.
* @param key the key of the entry that was updated
@@ -245,7 +248,8 @@
if (shouldHeadsUpAgain) {
headsUpEntry.updateEntry(true /* updatePostTime */, "updateNotification");
if (headsUpEntry != null) {
- setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUpEntry.mEntry));
+ setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUpEntry.mEntry),
+ "updateNotificationInternal");
}
}
}
@@ -264,10 +268,10 @@
List<String> waitingKeysToRemove = mAvalancheController.getWaitingKeys();
for (String key : keysToRemove) {
- removeEntry(key);
+ removeEntry(key, "releaseAllImmediately (keysToRemove)");
}
for (String key : waitingKeysToRemove) {
- removeEntry(key);
+ removeEntry(key, "releaseAllImmediately (waitingKeysToRemove)");
}
}
@@ -338,8 +342,9 @@
}
protected void setEntryPinned(
- @NonNull BaseHeadsUpManager.HeadsUpEntry headsUpEntry, boolean isPinned) {
- mLogger.logSetEntryPinned(headsUpEntry.mEntry, isPinned);
+ @NonNull BaseHeadsUpManager.HeadsUpEntry headsUpEntry, boolean isPinned,
+ String reason) {
+ mLogger.logSetEntryPinned(headsUpEntry.mEntry, isPinned, reason);
NotificationEntry entry = headsUpEntry.mEntry;
if (!isPinned) {
headsUpEntry.mWasUnpinned = true;
@@ -376,7 +381,7 @@
entry.setHeadsUp(true);
final boolean shouldPin = shouldHeadsUpBecomePinned(entry);
- setEntryPinned(headsUpEntry, shouldPin);
+ setEntryPinned(headsUpEntry, shouldPin, "onEntryAdded");
EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */);
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpStateChanged(entry, true);
@@ -387,17 +392,24 @@
* Remove a notification from the alerting entries.
* @param key key of notification to remove
*/
- protected final void removeEntry(@NonNull String key) {
+ protected final void removeEntry(@NonNull String key, String reason) {
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
- mLogger.logRemoveEntryRequest(key);
-
+ boolean isWaiting;
+ if (headsUpEntry == null) {
+ headsUpEntry = mAvalancheController.getWaitingEntry(key);
+ isWaiting = true;
+ } else {
+ isWaiting = false;
+ }
+ mLogger.logRemoveEntryRequest(key, reason, isWaiting);
+ HeadsUpEntry finalHeadsUpEntry = headsUpEntry;
Runnable runnable = () -> {
- mLogger.logRemoveEntry(key);
+ mLogger.logRemoveEntry(key, reason, isWaiting);
- if (headsUpEntry == null) {
+ if (finalHeadsUpEntry == null) {
return;
}
- NotificationEntry entry = headsUpEntry.mEntry;
+ NotificationEntry entry = finalHeadsUpEntry.mEntry;
// If the notification is animating, we will remove it at the end of the animation.
if (entry != null && entry.isExpandAnimationRunning()) {
@@ -405,13 +417,13 @@
}
entry.demoteStickyHun();
mHeadsUpEntryMap.remove(key);
- onEntryRemoved(headsUpEntry);
+ onEntryRemoved(finalHeadsUpEntry);
// TODO(b/328390331) move accessibility events to the view layer
entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
if (NotificationsHeadsUpRefactor.isEnabled()) {
- headsUpEntry.cancelAutoRemovalCallbacks("removeEntry");
+ finalHeadsUpEntry.cancelAutoRemovalCallbacks("removeEntry");
} else {
- headsUpEntry.reset();
+ finalHeadsUpEntry.reset();
}
};
mAvalancheController.delete(headsUpEntry, runnable, "removeEntry");
@@ -424,7 +436,7 @@
protected void onEntryRemoved(HeadsUpEntry headsUpEntry) {
NotificationEntry entry = headsUpEntry.mEntry;
entry.setHeadsUp(false);
- setEntryPinned(headsUpEntry, false /* isPinned */);
+ setEntryPinned(headsUpEntry, false /* isPinned */, "onEntryRemoved");
EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
mLogger.logNotificationActuallyRemoved(entry);
for (OnHeadsUpChangedListener listener : mListeners) {
@@ -584,7 +596,7 @@
Runnable runnable = () -> {
mLogger.logUnpinEntry(key);
- setEntryPinned(headsUpEntry, false /* isPinned */);
+ setEntryPinned(headsUpEntry, false /* isPinned */, "unpinAll");
// maybe it got un sticky
headsUpEntry.updateEntry(false /* updatePostTime */, "unpinAll");
@@ -985,7 +997,7 @@
/** Creates a runnable to remove this notification from the alerting entries. */
protected Runnable createRemoveRunnable(NotificationEntry entry) {
- return () -> removeEntry(entry.getKey());
+ return () -> removeEntry(entry.getKey(), "createRemoveRunnable");
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index 11cbc9c..6ffb162 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -121,19 +121,23 @@
})
}
- fun logRemoveEntryRequest(key: String) {
+ fun logRemoveEntryRequest(key: String, reason: String, isWaiting: Boolean) {
buffer.log(TAG, INFO, {
str1 = logKey(key)
+ str2 = reason
+ bool1 = isWaiting
}, {
- "request: remove entry $str1"
+ "request: $str2 => remove entry $str1 isWaiting: $isWaiting"
})
}
- fun logRemoveEntry(key: String) {
+ fun logRemoveEntry(key: String, reason: String, isWaiting: Boolean) {
buffer.log(TAG, INFO, {
str1 = logKey(key)
+ str2 = reason
+ bool1 = isWaiting
}, {
- "remove entry $str1"
+ "$str2 => remove entry $str1 isWaiting: $isWaiting"
})
}
@@ -153,12 +157,13 @@
})
}
- fun logRemoveNotification(key: String, releaseImmediately: Boolean) {
+ fun logRemoveNotification(key: String, releaseImmediately: Boolean, isWaiting: Boolean) {
buffer.log(TAG, INFO, {
str1 = logKey(key)
bool1 = releaseImmediately
+ bool2 = isWaiting
}, {
- "remove notification $str1 releaseImmediately: $bool1"
+ "remove notification $str1 releaseImmediately: $bool1 isWaiting: $bool2"
})
}
@@ -208,12 +213,13 @@
})
}
- fun logSetEntryPinned(entry: NotificationEntry, isPinned: Boolean) {
+ fun logSetEntryPinned(entry: NotificationEntry, isPinned: Boolean, reason: String) {
buffer.log(TAG, VERBOSE, {
str1 = entry.logKey
bool1 = isPinned
+ str2 = reason
}, {
- "set entry pinned $str1 pinned: $bool1"
+ "$str2 => set entry pinned $str1 pinned: $bool1"
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index f4fc978..1ae5614 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.dagger
+import android.content.ContentResolver
import android.content.Context
import android.media.AudioManager
import com.android.settingslib.bluetooth.LocalBluetoothManager
@@ -28,6 +29,7 @@
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
import com.android.settingslib.volume.shared.AudioManagerEventsReceiverImpl
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import dagger.Module
@@ -42,21 +44,31 @@
companion object {
@Provides
+ @SysUISingleton
fun provideAudioManagerIntentsReceiver(
@Application context: Context,
@Application coroutineScope: CoroutineScope,
): AudioManagerEventsReceiver = AudioManagerEventsReceiverImpl(context, coroutineScope)
@Provides
+ @SysUISingleton
fun provideAudioRepository(
intentsReceiver: AudioManagerEventsReceiver,
audioManager: AudioManager,
+ contentResolver: ContentResolver,
@Background coroutineContext: CoroutineContext,
@Application coroutineScope: CoroutineScope,
): AudioRepository =
- AudioRepositoryImpl(intentsReceiver, audioManager, coroutineContext, coroutineScope)
+ AudioRepositoryImpl(
+ intentsReceiver,
+ audioManager,
+ contentResolver,
+ coroutineContext,
+ coroutineScope,
+ )
@Provides
+ @SysUISingleton
fun provideAudioSharingRepository(
localBluetoothManager: LocalBluetoothManager?,
@Background coroutineContext: CoroutineContext,
@@ -64,10 +76,12 @@
AudioSharingRepositoryImpl(localBluetoothManager, coroutineContext)
@Provides
+ @SysUISingleton
fun provideAudioModeInteractor(repository: AudioRepository): AudioModeInteractor =
AudioModeInteractor(repository)
@Provides
+ @SysUISingleton
fun provideAudioVolumeInteractor(
audioRepository: AudioRepository,
notificationsSoundPolicyInteractor: NotificationsSoundPolicyInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/CaptioningModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/CaptioningModule.kt
index ea67eea..73f5237 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/CaptioningModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/CaptioningModule.kt
@@ -20,6 +20,7 @@
import com.android.settingslib.view.accessibility.data.repository.CaptioningRepository
import com.android.settingslib.view.accessibility.data.repository.CaptioningRepositoryImpl
import com.android.settingslib.view.accessibility.domain.interactor.CaptioningInteractor
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import dagger.Module
@@ -33,6 +34,7 @@
companion object {
@Provides
+ @SysUISingleton
fun provideCaptioningRepository(
captioningManager: CaptioningManager,
@Background coroutineContext: CoroutineContext,
@@ -41,6 +43,7 @@
CaptioningRepositoryImpl(captioningManager, coroutineContext, coroutineScope)
@Provides
+ @SysUISingleton
fun provideCaptioningInteractor(repository: CaptioningRepository): CaptioningInteractor =
CaptioningInteractor(repository)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
index 3696108..efab199 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
@@ -18,7 +18,6 @@
import android.media.session.MediaSessionManager
import com.android.settingslib.bluetooth.LocalBluetoothManager
-import com.android.settingslib.volume.data.repository.LocalMediaRepository
import com.android.settingslib.volume.data.repository.MediaControllerRepository
import com.android.settingslib.volume.data.repository.MediaControllerRepositoryImpl
import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
@@ -52,12 +51,6 @@
@Provides
@SysUISingleton
- fun provideLocalMediaRepository(
- factory: LocalMediaRepositoryFactory
- ): LocalMediaRepository = factory.create(null)
-
- @Provides
- @SysUISingleton
fun provideMediaDeviceSessionRepository(
intentsReceiver: AudioManagerEventsReceiver,
mediaSessionManager: MediaSessionManager,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt
index 4ba7cbb..a11997a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt
@@ -21,6 +21,7 @@
import com.android.settingslib.media.data.repository.SpatializerRepository
import com.android.settingslib.media.data.repository.SpatializerRepositoryImpl
import com.android.settingslib.media.domain.interactor.SpatializerInteractor
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import dagger.Module
import dagger.Provides
@@ -33,17 +34,20 @@
companion object {
@Provides
+ @SysUISingleton
fun provideSpatializer(
audioManager: AudioManager,
): Spatializer = audioManager.spatializer
@Provides
+ @SysUISingleton
fun provdieSpatializerRepository(
spatializer: Spatializer,
@Background backgroundContext: CoroutineContext,
): SpatializerRepository = SpatializerRepositoryImpl(spatializer, backgroundContext)
@Provides
+ @SysUISingleton
fun provideSpatializerInetractor(repository: SpatializerRepository): SpatializerInteractor =
SpatializerInteractor(repository)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
index e052f24..0dc2647 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
@@ -18,6 +18,7 @@
import com.android.settingslib.volume.data.repository.LocalMediaRepository
import com.android.settingslib.volume.data.repository.LocalMediaRepositoryImpl
import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.controls.util.LocalMediaManagerFactory
import javax.inject.Inject
@@ -28,6 +29,7 @@
fun create(packageName: String?): LocalMediaRepository
}
+@SysUISingleton
class LocalMediaRepositoryFactoryImpl
@Inject
constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepository.kt
new file mode 100644
index 0000000..e46ce26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepository.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.data.repository
+
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.volume.panel.shared.model.VolumePanelGlobalState
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+private const val TAG = "VolumePanelGlobalState"
+
+@SysUISingleton
+class VolumePanelGlobalStateRepository @Inject constructor(dumpManager: DumpManager) : Dumpable {
+
+ private val mutableGlobalState =
+ MutableStateFlow(
+ VolumePanelGlobalState(
+ isVisible = false,
+ )
+ )
+ val globalState: StateFlow<VolumePanelGlobalState> = mutableGlobalState.asStateFlow()
+
+ init {
+ dumpManager.registerNormalDumpable(TAG, this)
+ }
+
+ fun updateVolumePanelState(
+ update: (currentState: VolumePanelGlobalState) -> VolumePanelGlobalState
+ ) {
+ mutableGlobalState.update(update)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ with(globalState.value) { pw.println("isVisible: $isVisible") }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/VolumePanelGlobalStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/VolumePanelGlobalStateInteractor.kt
new file mode 100644
index 0000000..e930aca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/VolumePanelGlobalStateInteractor.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.volume.panel.data.repository.VolumePanelGlobalStateRepository
+import com.android.systemui.volume.panel.shared.model.VolumePanelGlobalState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+@SysUISingleton
+class VolumePanelGlobalStateInteractor
+@Inject
+constructor(
+ private val repository: VolumePanelGlobalStateRepository,
+) {
+
+ val globalState: StateFlow<VolumePanelGlobalState>
+ get() = repository.globalState
+
+ fun setVisible(isVisible: Boolean) {
+ repository.updateVolumePanelState { it.copy(isVisible = isVisible) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelGlobalState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelGlobalState.kt
new file mode 100644
index 0000000..fac49db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelGlobalState.kt
@@ -0,0 +1,19 @@
+/*
+ * 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.shared.model
+
+data class VolumePanelGlobalState(val isVisible: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt
index f57e293..dc43155 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt
@@ -29,7 +29,6 @@
data class VolumePanelState(
@Orientation val orientation: Int,
val isLargeScreen: Boolean,
- val isVisible: Boolean,
) {
init {
require(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
index a30de1b..f495a02f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
@@ -29,23 +29,22 @@
import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
import com.android.systemui.volume.panel.domain.VolumePanelStartable
import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
+import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor
import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.flow.update
// Can't inject a constructor here because VolumePanelComponent provides this view model for its
// components.
@@ -55,6 +54,7 @@
daggerComponentFactory: VolumePanelComponentFactory,
configurationController: ConfigurationController,
broadcastDispatcher: BroadcastDispatcher,
+ private val volumePanelGlobalStateInteractor: VolumePanelGlobalStateInteractor,
) {
private val volumePanelComponent: VolumePanelComponent =
@@ -72,18 +72,12 @@
private val componentsLayoutManager: ComponentsLayoutManager
get() = volumePanelComponent.componentsLayoutManager()
- private val mutablePanelVisibility = MutableStateFlow(true)
-
val volumePanelState: StateFlow<VolumePanelState> =
- combine(
- configurationController.onConfigChanged
- .onStart { emit(resources.configuration) }
- .distinctUntilChanged(),
- mutablePanelVisibility,
- ) { configuration, isVisible ->
+ configurationController.onConfigChanged
+ .onStart { emit(resources.configuration) }
+ .map { configuration ->
VolumePanelState(
orientation = configuration.orientation,
- isVisible = isVisible,
isLargeScreen = resources.getBoolean(R.bool.volume_panel_is_large_screen),
)
}
@@ -92,7 +86,6 @@
SharingStarted.Eagerly,
VolumePanelState(
orientation = resources.configuration.orientation,
- isVisible = mutablePanelVisibility.value,
isLargeScreen = resources.getBoolean(R.bool.volume_panel_is_large_screen)
),
)
@@ -126,7 +119,7 @@
}
fun dismissPanel() {
- mutablePanelVisibility.update { false }
+ volumePanelGlobalStateInteractor.setVisible(false)
}
class Factory
@@ -136,6 +129,7 @@
private val daggerComponentFactory: VolumePanelComponentFactory,
private val configurationController: ConfigurationController,
private val broadcastDispatcher: BroadcastDispatcher,
+ private val volumePanelGlobalStateInteractor: VolumePanelGlobalStateInteractor,
) {
fun create(coroutineScope: CoroutineScope): VolumePanelViewModel {
@@ -144,7 +138,8 @@
coroutineScope,
daggerComponentFactory,
configurationController,
- broadcastDispatcher
+ broadcastDispatcher,
+ volumePanelGlobalStateInteractor,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt b/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt
index 905a749..03c8af90 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt
@@ -16,26 +16,40 @@
package com.android.systemui.volume.ui.navigation
+import android.app.Dialog
import android.content.Intent
import android.provider.Settings
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import com.android.systemui.statusbar.phone.createBottomSheet
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.volume.VolumePanelFactory
import com.android.systemui.volume.domain.model.VolumePanelRoute
+import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor
import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import com.android.systemui.volume.panel.ui.composable.VolumePanelRoot
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
class VolumeNavigator
@Inject
constructor(
@@ -46,8 +60,29 @@
private val viewModelFactory: VolumePanelViewModel.Factory,
private val dialogFactory: SystemUIDialogFactory,
private val uiEventLogger: UiEventLogger,
+ private val volumePanelGlobalStateInteractor: VolumePanelGlobalStateInteractor,
) {
+ init {
+ volumePanelGlobalStateInteractor.globalState
+ .map { it.isVisible }
+ .distinctUntilChanged()
+ .flatMapLatest { isVisible ->
+ if (isVisible) {
+ conflatedCallbackFlow<Unit> {
+ val dialog = createNewVolumePanelDialog()
+ uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_SHOWN)
+ dialog.show()
+ awaitClose { dialog.dismiss() }
+ }
+ .flowOn(mainContext)
+ } else {
+ emptyFlow()
+ }
+ }
+ .launchIn(applicationScope)
+ }
+
fun openVolumePanel(route: VolumePanelRoute) {
when (route) {
VolumePanelRoute.COMPOSE_VOLUME_PANEL -> showNewVolumePanel()
@@ -62,24 +97,24 @@
}
private fun showNewVolumePanel() {
- applicationScope.launch(mainContext) {
- uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_SHOWN)
- dialogFactory
- .createBottomSheet(
- content = { dialog ->
- LaunchedEffect(dialog) {
- dialog.setOnDismissListener {
- uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_GONE)
- }
- }
+ volumePanelGlobalStateInteractor.setVisible(true)
+ }
- VolumePanelRoot(
- viewModel = viewModelFactory.create(rememberCoroutineScope()),
- onDismiss = { dialog.dismiss() },
- )
- },
+ private fun createNewVolumePanelDialog(): Dialog {
+ return dialogFactory.createBottomSheet(
+ content = { dialog ->
+ LaunchedEffect(dialog) {
+ dialog.setOnDismissListener {
+ uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_GONE)
+ volumePanelGlobalStateInteractor.setVisible(false)
+ }
+ }
+
+ val coroutineScope = rememberCoroutineScope()
+ VolumePanelRoot(
+ remember(coroutineScope) { viewModelFactory.create(coroutineScope) }
)
- .show()
- }
+ },
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
index 190babd..0ed84ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
@@ -4,7 +4,9 @@
import android.window.BackEvent
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.dpToPx
import com.google.common.truth.Truth.assertThat
+import junit.framework.TestCase.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -60,6 +62,58 @@
expected = BackTransformation(translateX = 0f, translateY = maxY, scale = 1f),
)
}
+
+ @Test
+ fun sysUi_bottomsheet_animationValues() {
+ val minScale = 1 - 48.dpToPx(displayMetrics) / displayMetrics.widthPixels
+
+ val backAnimationSpec = BackAnimationSpec.bottomSheetForSysUi { displayMetrics }
+
+ assertBackTransformation(
+ backAnimationSpec = backAnimationSpec,
+ backInput = BackInput(progressX = 0f, progressY = 0f, edge = BackEvent.EDGE_LEFT),
+ expected =
+ BackTransformation(
+ translateX = Float.NaN,
+ translateY = Float.NaN,
+ scale = 1f,
+ scalePivotPosition = ScalePivotPosition.BOTTOM_CENTER
+ ),
+ )
+ assertBackTransformation(
+ backAnimationSpec = backAnimationSpec,
+ backInput = BackInput(progressX = 1f, progressY = 0f, edge = BackEvent.EDGE_LEFT),
+ expected =
+ BackTransformation(
+ translateX = Float.NaN,
+ translateY = Float.NaN,
+ scale = minScale,
+ scalePivotPosition = ScalePivotPosition.BOTTOM_CENTER
+ ),
+ )
+ assertBackTransformation(
+ backAnimationSpec = backAnimationSpec,
+ backInput = BackInput(progressX = 1f, progressY = 0f, edge = BackEvent.EDGE_RIGHT),
+ expected =
+ BackTransformation(
+ translateX = Float.NaN,
+ translateY = Float.NaN,
+ scale = minScale,
+ scalePivotPosition = ScalePivotPosition.BOTTOM_CENTER
+ ),
+ )
+ assertBackTransformation(
+ backAnimationSpec = backAnimationSpec,
+ backInput = BackInput(progressX = 1f, progressY = 1f, edge = BackEvent.EDGE_LEFT),
+ expected =
+ BackTransformation(
+ translateX = Float.NaN,
+ translateY = Float.NaN,
+ scale = minScale,
+ scalePivotPosition = ScalePivotPosition.BOTTOM_CENTER
+ ),
+ )
+ }
}
private fun assertBackTransformation(
@@ -81,7 +135,16 @@
)
val tolerance = 0f
- assertThat(actual.translateX).isWithin(tolerance).of(expected.translateX)
- assertThat(actual.translateY).isWithin(tolerance).of(expected.translateY)
+ if (expected.translateX.isNaN()) {
+ assertEquals(expected.translateX, actual.translateX)
+ } else {
+ assertThat(actual.translateX).isWithin(tolerance).of(expected.translateX)
+ }
+ if (expected.translateY.isNaN()) {
+ assertEquals(expected.translateY, actual.translateY)
+ } else {
+ assertThat(actual.translateY).isWithin(tolerance).of(expected.translateY)
+ }
assertThat(actual.scale).isWithin(tolerance).of(expected.scale)
+ assertEquals(expected.scalePivotPosition, actual.scalePivotPosition)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt
index 190b3d2..44a5467 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt
@@ -5,17 +5,25 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(JUnit4::class)
class BackTransformationTest : SysuiTestCase() {
private val targetView: View = mock()
+ @Before
+ fun setup() {
+ whenever(targetView.width).thenReturn(TARGET_VIEW_WIDTH)
+ whenever(targetView.height).thenReturn(TARGET_VIEW_HEIGHT)
+ }
+
@Test
fun defaultValue_noTransformation() {
val transformation = BackTransformation()
@@ -70,6 +78,16 @@
}
@Test
+ fun applyTo_targetView_scale_pivot() {
+ val transformation = BackTransformation(scalePivotPosition = ScalePivotPosition.CENTER)
+
+ transformation.applyTo(targetView = targetView)
+
+ verify(targetView).pivotX = TARGET_VIEW_WIDTH / 2f
+ verify(targetView).pivotY = TARGET_VIEW_HEIGHT / 2f
+ }
+
+ @Test
fun applyTo_targetView_noTransformation() {
val transformation = BackTransformation()
@@ -77,4 +95,9 @@
verifyNoMoreInteractions(targetView)
}
+
+ companion object {
+ private const val TARGET_VIEW_WIDTH = 100
+ private const val TARGET_VIEW_HEIGHT = 50
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
index 472d045..8be1e7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -115,6 +115,7 @@
{ mock(DeviceEntryFingerprintAuthInteractor::class.java) },
{ mock(KeyguardInteractor::class.java) },
{ mock(KeyguardTransitionInteractor::class.java) },
+ { kosmos.sceneInteractor },
testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index fa3fe5c..e02fb29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.dock.fakeDockManager
import com.android.systemui.flags.BrokenWithSceneContainer
import com.android.systemui.flags.DisableSceneContainer
@@ -511,7 +512,6 @@
.startedTransition(
to = KeyguardState.LOCKSCREEN,
from = KeyguardState.DOZING,
- ownerName = "FromDozingTransitionInteractor",
animatorAssertion = { it.isNotNull() }
)
@@ -600,7 +600,6 @@
.startedTransition(
to = KeyguardState.PRIMARY_BOUNCER,
from = KeyguardState.DOZING,
- ownerName = "FromDozingTransitionInteractor",
animatorAssertion = { it.isNotNull() }
)
@@ -618,6 +617,7 @@
// WHEN the device wakes up without a keyguard
keyguardRepository.setKeyguardShowing(false)
keyguardRepository.setKeyguardDismissible(true)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(false)
powerInteractor.setAwakeForTest()
advanceTimeBy(60L)
@@ -625,7 +625,6 @@
.startedTransition(
to = KeyguardState.GONE,
from = KeyguardState.DOZING,
- ownerName = "FromDozingTransitionInteractor",
animatorAssertion = { it.isNotNull() }
)
@@ -683,7 +682,6 @@
.startedTransition(
to = KeyguardState.GLANCEABLE_HUB,
from = KeyguardState.DOZING,
- ownerName = FromDozingTransitionInteractor::class.simpleName,
animatorAssertion = { it.isNotNull() }
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
index 6043ede..ef73e2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.mediaprojection.taskswitcher.data.repository
+package com.android.systemui.mediaprojection.data.repository
import android.os.Binder
import android.testing.AndroidTestingRunner
@@ -23,9 +23,9 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createToken
-import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
import com.android.systemui.mediaprojection.taskswitcher.mediaProjectionManagerRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
index 9382c58..ad18099 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
@@ -40,6 +40,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
@RunWith(AndroidTestingRunner::class)
@@ -66,6 +67,10 @@
fakeBroadcastDispatcher,
)
coordinator.start()
+ // When the coordinator starts up, the view model will immediately emit a NotShowing event
+ // and hide the notification. That's fine, but we should reset the notification manager so
+ // that the initial emission isn't part of the tests.
+ reset(notificationManager)
}
@Test
@@ -82,8 +87,13 @@
@Test
fun hideNotification() {
testScope.runTest {
+ // First, show a notification
+ switchTask()
+
+ // WHEN the projection is stopped
fakeMediaProjectionManager.dispatchOnStop()
+ // THEN the notification is hidden
verify(notificationManager).cancel(any(), any())
}
}
@@ -91,14 +101,16 @@
@Test
fun notificationIdIsConsistent() {
testScope.runTest {
- fakeMediaProjectionManager.dispatchOnStop()
- val idCancel = argumentCaptor<Int>()
- verify(notificationManager).cancel(any(), idCancel.capture())
-
+ // First, show a notification
switchTask()
val idNotify = argumentCaptor<Int>()
verify(notificationManager).notify(any(), idNotify.capture(), any())
+ // Then, hide the notification
+ fakeMediaProjectionManager.dispatchOnStop()
+ val idCancel = argumentCaptor<Int>()
+ verify(notificationManager).cancel(any(), idCancel.capture())
+
assertEquals(idCancel.value, idNotify.value)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt
new file mode 100644
index 0000000..07ec38e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt
@@ -0,0 +1,300 @@
+/*
+ * 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.qs
+
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.testing.TestableLooper.RunWithLooper
+import android.view.ViewTreeObserver
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.haptics.qs.QSLongPressEffect
+import com.android.systemui.haptics.qs.qsLongPressEffect
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.InstantTaskExecutorRule
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.qs.customize.QSCustomizerController
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.statusbar.policy.SplitShadeStateController
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import javax.inject.Provider
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
+@EnableSceneContainer
+class QSPanelControllerBaseSceneContainerTest : SysuiTestCase() {
+
+ @Rule @JvmField val mInstantTaskExecutor = InstantTaskExecutorRule()
+
+ private val kosmos = testKosmos()
+
+ @Mock private lateinit var qsPanel: QSPanel
+ @Mock private lateinit var qsHost: QSHost
+ @Mock private lateinit var qsCustomizerController: QSCustomizerController
+ @Mock private lateinit var metricsLogger: MetricsLogger
+ private val uiEventLogger = UiEventLoggerFake()
+ @Mock private lateinit var qsLogger: QSLogger
+ private val dumpManager = DumpManager()
+ @Mock private lateinit var tileLayout: PagedTileLayout
+ @Mock private lateinit var resources: Resources
+ private val configuration = Configuration()
+ @Mock private lateinit var viewTreeObserver: ViewTreeObserver
+ @Mock private lateinit var mediaHost: MediaHost
+
+ private var isSplitShade = false
+ private val splitShadeStateController =
+ object : SplitShadeStateController {
+ override fun shouldUseSplitNotificationShade(resources: Resources): Boolean {
+ return isSplitShade
+ }
+ }
+ private val longPressEffectProvider: Provider<QSLongPressEffect> = Provider {
+ kosmos.qsLongPressEffect
+ }
+
+ private val mediaVisible = MutableStateFlow(false)
+
+ private lateinit var underTest: TestableQSPanelControllerBase
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ allowTestableLooperAsMainThread()
+ Dispatchers.setMain(kosmos.testDispatcher)
+
+ whenever(qsPanel.isAttachedToWindow).thenReturn(true)
+ whenever(qsPanel.orCreateTileLayout).thenReturn(tileLayout)
+ whenever(qsPanel.tileLayout).thenReturn(tileLayout)
+ whenever(qsPanel.resources).thenReturn(resources)
+ whenever(qsPanel.viewTreeObserver).thenReturn(viewTreeObserver)
+ whenever(qsHost.tiles).thenReturn(emptyList())
+ whenever(resources.configuration).thenReturn(configuration)
+
+ underTest = createUnderTest()
+ underTest.init()
+ }
+
+ @After
+ fun tearDown() {
+ disallowTestableLooperAsMainThread()
+ Dispatchers.resetMain()
+ }
+
+ @Test
+ fun configurationChange_onlySplitShadeConfigChanges_horizontalInSceneUpdated() =
+ with(kosmos) {
+ testScope.runTest {
+ clearInvocations(qsPanel)
+
+ mediaVisible.value = true
+ runCurrent()
+ isSplitShade = false
+ configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
+ configuration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES
+ underTest.mOnConfigurationChangedListener.onConfigurationChange(configuration)
+
+ assertThat(underTest.shouldUseHorizontalInScene()).isTrue()
+ verify(qsPanel).setColumnRowLayout(true)
+ clearInvocations(qsPanel)
+
+ isSplitShade = true
+ underTest.mOnConfigurationChangedListener.onConfigurationChange(configuration)
+
+ assertThat(underTest.shouldUseHorizontalInScene()).isFalse()
+ verify(qsPanel).setColumnRowLayout(false)
+ }
+ }
+
+ @Test
+ fun configurationChange_shouldUseHorizontalInSceneInLongDevices() =
+ with(kosmos) {
+ testScope.runTest {
+ clearInvocations(qsPanel)
+
+ mediaVisible.value = true
+ runCurrent()
+ isSplitShade = false
+ // When device is rotated to landscape and is long
+ configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
+ configuration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES
+ underTest.mOnConfigurationChangedListener.onConfigurationChange(configuration)
+
+ // Then the layout changes
+ assertThat(underTest.shouldUseHorizontalInScene()).isTrue()
+ verify(qsPanel).setColumnRowLayout(true)
+ clearInvocations(qsPanel)
+
+ // When device changes to not-long
+ configuration.screenLayout = Configuration.SCREENLAYOUT_LONG_NO
+ underTest.mOnConfigurationChangedListener.onConfigurationChange(configuration)
+
+ // Then the layout changes back
+ assertThat(underTest.shouldUseHorizontalInScene()).isFalse()
+ verify(qsPanel).setColumnRowLayout(false)
+ }
+ }
+
+ @Test
+ fun configurationChange_horizontalInScene_onlyInLandscape() =
+ with(kosmos) {
+ testScope.runTest {
+ clearInvocations(qsPanel)
+
+ mediaVisible.value = true
+ runCurrent()
+ isSplitShade = false
+
+ // When device is rotated to landscape and is long
+ configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
+ configuration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES
+ underTest.mOnConfigurationChangedListener.onConfigurationChange(configuration)
+
+ // Then the layout changes
+ assertThat(underTest.shouldUseHorizontalInScene()).isTrue()
+ verify(qsPanel).setColumnRowLayout(true)
+ clearInvocations(qsPanel)
+
+ // When it is rotated back to portrait
+ configuration.orientation = Configuration.ORIENTATION_PORTRAIT
+ underTest.mOnConfigurationChangedListener.onConfigurationChange(configuration)
+
+ // Then the layout changes back
+ assertThat(underTest.shouldUseHorizontalInScene()).isFalse()
+ verify(qsPanel).setColumnRowLayout(false)
+ }
+ }
+
+ @Test
+ fun changeMediaVisible_changesHorizontalInScene() =
+ with(kosmos) {
+ testScope.runTest {
+ mediaVisible.value = false
+ runCurrent()
+ isSplitShade = false
+ configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
+ configuration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES
+ underTest.mOnConfigurationChangedListener.onConfigurationChange(configuration)
+
+ assertThat(underTest.shouldUseHorizontalInScene()).isFalse()
+ clearInvocations(qsPanel)
+
+ mediaVisible.value = true
+ runCurrent()
+
+ assertThat(underTest.shouldUseHorizontalInScene()).isTrue()
+ verify(qsPanel).setColumnRowLayout(true)
+ }
+ }
+
+ @Test
+ fun startFromMediaHorizontalLong_shouldUseHorizontal() =
+ with(kosmos) {
+ testScope.runTest {
+ mediaVisible.value = true
+ runCurrent()
+ isSplitShade = false
+ configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
+ configuration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES
+
+ underTest = createUnderTest()
+ underTest.init()
+ runCurrent()
+
+ assertThat(underTest.shouldUseHorizontalInScene()).isTrue()
+ verify(qsPanel).setColumnRowLayout(true)
+ }
+ }
+
+ private fun createUnderTest(): TestableQSPanelControllerBase {
+ return TestableQSPanelControllerBase(
+ qsPanel,
+ qsHost,
+ qsCustomizerController,
+ mediaHost,
+ metricsLogger,
+ uiEventLogger,
+ qsLogger,
+ dumpManager,
+ splitShadeStateController,
+ longPressEffectProvider,
+ mediaVisible,
+ )
+ }
+
+ private class TestableQSPanelControllerBase(
+ view: QSPanel,
+ qsHost: QSHost,
+ qsCustomizerController: QSCustomizerController,
+ mediaHost: MediaHost,
+ metricsLogger: MetricsLogger,
+ uiEventLogger: UiEventLogger,
+ qsLogger: QSLogger,
+ dumpManager: DumpManager,
+ splitShadeStateController: SplitShadeStateController,
+ longPressEffectProvider: Provider<QSLongPressEffect>,
+ private val mediaVisibleFlow: StateFlow<Boolean>
+ ) :
+ QSPanelControllerBase<QSPanel>(
+ view,
+ qsHost,
+ qsCustomizerController,
+ /* usingMediaPlayer= */ false,
+ mediaHost,
+ metricsLogger,
+ uiEventLogger,
+ qsLogger,
+ dumpManager,
+ splitShadeStateController,
+ longPressEffectProvider
+ ) {
+
+ init {
+ whenever(view.dumpableTag).thenReturn(hashCode().toString())
+ }
+ override fun getMediaVisibleFlow(): StateFlow<Boolean> {
+ return mediaVisibleFlow
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 542bfaa..225adab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -17,9 +17,13 @@
package com.android.systemui.qs;
import static com.android.systemui.Flags.FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS;
+import static com.android.systemui.flags.SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag;
import static com.google.common.truth.Truth.assertThat;
+import static kotlinx.coroutines.flow.FlowKt.asStateFlow;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -37,9 +41,10 @@
import android.content.res.Resources;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
-import android.testing.AndroidTestingRunner;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.testing.TestableLooper.RunWithLooper;
import android.view.ContextThemeWrapper;
+import android.view.ViewTreeObserver;
import androidx.test.filters.SmallTest;
@@ -49,18 +54,26 @@
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.DisableSceneContainer;
import com.android.systemui.haptics.qs.QSLongPressEffect;
import com.android.systemui.kosmos.KosmosJavaAdapter;
+import com.android.systemui.lifecycle.InstantTaskExecutorRule;
import com.android.systemui.media.controls.ui.view.MediaHost;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.util.animation.DisappearParameters;
+import kotlinx.coroutines.flow.MutableStateFlow;
+import kotlinx.coroutines.flow.StateFlow;
+
+import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -73,11 +86,22 @@
import javax.inject.Provider;
-@RunWith(AndroidTestingRunner.class)
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
+@RunWith(ParameterizedAndroidJunit4.class)
@RunWithLooper
@SmallTest
public class QSPanelControllerBaseTest extends SysuiTestCase {
+ @Rule
+ public final InstantTaskExecutorRule mInstantTaskExecutor = new InstantTaskExecutorRule();
+
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return parameterizeSceneContainerFlag();
+ }
+
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
@Mock
private QSPanel mQSPanel;
@@ -109,10 +133,13 @@
Configuration mConfiguration;
@Mock
Runnable mHorizontalLayoutListener;
+ @Mock
+ private ViewTreeObserver mViewTreeObserver;
+
private TestableLongPressEffectProvider mLongPressEffectProvider =
new TestableLongPressEffectProvider();
- private QSPanelControllerBase<QSPanel> mController;
+ private TestableQSPanelControllerBase mController;
/** Implementation needed to ensure we have a reflectively-available class name. */
private class TestableQSPanelControllerBase extends QSPanelControllerBase<QSPanel> {
@@ -120,15 +147,27 @@
QSCustomizerController qsCustomizerController, MediaHost mediaHost,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
DumpManager dumpManager) {
- super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger,
+ super(view, host, qsCustomizerController, usingMediaPlayer(),
+ mediaHost, metricsLogger, uiEventLogger,
qsLogger, dumpManager, new ResourcesSplitShadeStateController(),
mLongPressEffectProvider);
}
+ private MutableStateFlow<Boolean> mMediaVisible = MutableStateFlow(false);
+
@Override
protected QSTileRevealController createTileRevealController() {
return mQSTileRevealController;
}
+
+ @Override
+ StateFlow<Boolean> getMediaVisibleFlow() {
+ return asStateFlow(mMediaVisible);
+ }
+
+ void setMediaVisible(boolean visible) {
+ mMediaVisible.tryEmit(visible);
+ }
}
private class TestableLongPressEffectProvider implements Provider<QSLongPressEffect> {
@@ -142,16 +181,24 @@
}
}
+ public QSPanelControllerBaseTest(FlagsParameterization flags) {
+ super();
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
+ allowTestableLooperAsMainThread();
+
when(mQSPanel.isAttachedToWindow()).thenReturn(true);
when(mQSPanel.getDumpableTag()).thenReturn("QSPanel");
when(mQSPanel.openPanelEvent()).thenReturn(QSEvent.QS_PANEL_EXPANDED);
when(mQSPanel.closePanelEvent()).thenReturn(QSEvent.QS_PANEL_COLLAPSED);
when(mQSPanel.getOrCreateTileLayout()).thenReturn(mPagedTileLayout);
when(mQSPanel.getTileLayout()).thenReturn(mPagedTileLayout);
+ when(mQSPanel.getViewTreeObserver()).thenReturn(mViewTreeObserver);
when(mQSTile.getTileSpec()).thenReturn("dnd");
when(mQSHost.getTiles()).thenReturn(Collections.singleton(mQSTile));
when(mQSTileRevealControllerFactory.create(any(), any()))
@@ -174,6 +221,11 @@
reset(mQSTileRevealController);
}
+ @After
+ public void tearDown() {
+ disallowTestableLooperAsMainThread();
+ }
+
@Test
public void testSetRevealExpansion_preAttach() {
mController.onViewDetached();
@@ -269,6 +321,7 @@
@Test
+ @DisableSceneContainer
public void testShouldUseHorizontalLayout_falseForSplitShade() {
mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
mConfiguration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES;
@@ -294,6 +347,7 @@
}
@Test
+ @DisableSceneContainer
public void testChangeConfiguration_shouldUseHorizontalLayoutInLandscape_true() {
when(mMediaHost.getVisible()).thenReturn(true);
mController.setUsingHorizontalLayoutChangeListener(mHorizontalLayoutListener);
@@ -317,6 +371,7 @@
}
@Test
+ @DisableSceneContainer
public void testChangeConfiguration_shouldUseHorizontalLayoutInLongDevices_true() {
when(mMediaHost.getVisible()).thenReturn(true);
mController.setUsingHorizontalLayoutChangeListener(mHorizontalLayoutListener);
@@ -353,6 +408,7 @@
}
@Test
+ @DisableSceneContainer
public void configurationChange_onlySplitShadeConfigChanges_horizontalLayoutStatusUpdated() {
// Preconditions for horizontal layout
when(mMediaHost.getVisible()).thenReturn(true);
@@ -502,4 +558,20 @@
verify(mQSPanel, times(2)).removeTile(any());
verify(mQSPanel, times(2)).addTile(any());
}
+
+ @Test
+ public void dettach_destroy_attach_tilesAreNotReadded() {
+ when(mQSHost.getTiles()).thenReturn(List.of(mQSTile, mOtherTile));
+ mController.setTiles();
+
+ mController.onViewDetached();
+ mController.destroy();
+ mController.onViewAttached();
+
+ assertThat(mController.mRecords).isEmpty();
+ }
+
+ private boolean usingMediaPlayer() {
+ return !SceneContainerFlag.isEnabled();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index e50320d..545d19d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -10,6 +10,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.haptics.qs.QSLongPressEffect
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.plugins.FalsingManager
@@ -17,6 +18,7 @@
import com.android.systemui.qs.customize.QSCustomizerController
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.settings.brightness.BrightnessController
import com.android.systemui.settings.brightness.BrightnessSliderController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
@@ -63,6 +65,9 @@
@Mock private lateinit var configuration: Configuration
@Mock private lateinit var pagedTileLayout: PagedTileLayout
@Mock private lateinit var longPressEffectProvider: Provider<QSLongPressEffect>
+ @Mock private lateinit var mediaCarouselInteractor: MediaCarouselInteractor
+
+ private val usingMediaPlayer: Boolean by lazy { !SceneContainerFlag.isEnabled }
private lateinit var controller: QSPanelController
private val testableResources: TestableResources = mContext.orCreateTestableResources
@@ -88,7 +93,7 @@
tunerService,
qsHost,
qsCustomizerController,
- /* usingMediaPlayer= */ true,
+ /* usingMediaPlayer= */ usingMediaPlayer,
mediaHost,
qsTileRevealControllerFactory,
dumpManager,
@@ -101,6 +106,7 @@
statusBarKeyguardViewManager,
ResourcesSplitShadeStateController(),
longPressEffectProvider,
+ mediaCarouselInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
index 5c6ed70..e2a4d67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -275,6 +275,19 @@
ViewUtils.detachView(panel)
}
+ @Test
+ fun setRowColumnLayout() {
+ qsPanel.setColumnRowLayout(/* withMedia= */ false)
+
+ assertThat(qsPanel.tileLayout!!.minRows).isEqualTo(1)
+ assertThat(qsPanel.tileLayout!!.maxColumns).isEqualTo(4)
+
+ qsPanel.setColumnRowLayout(/* withMedia= */ true)
+
+ assertThat(qsPanel.tileLayout!!.minRows).isEqualTo(2)
+ assertThat(qsPanel.tileLayout!!.maxColumns).isEqualTo(2)
+ }
+
private infix fun View.isLeftOf(other: View): Boolean {
val rect = Rect()
getBoundsOnScreen(rect)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 1eb0a51..fee4b53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.haptics.qs.QSLongPressEffect
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.plugins.qs.QSTile
@@ -62,6 +63,10 @@
@Mock private lateinit var tileLayout: TileLayout
@Captor private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
@Mock private lateinit var longPressEffectProvider: Provider<QSLongPressEffect>
+ @Mock private lateinit var mediaCarouselInteractor: MediaCarouselInteractor
+
+ private val usingMediaPlayer: Boolean
+ get() = false
private val uiEventLogger = UiEventLoggerFake()
private val dumpManager = DumpManager()
@@ -86,7 +91,7 @@
quickQSPanel,
qsHost,
qsCustomizerController,
- /* usingMediaPlayer = */ false,
+ usingMediaPlayer,
mediaHost,
{ usingCollapsedLandscapeMedia },
metricsLogger,
@@ -94,6 +99,7 @@
qsLogger,
dumpManager,
longPressEffectProvider,
+ mediaCarouselInteractor,
)
controller.init()
@@ -163,6 +169,7 @@
qsLogger: QSLogger,
dumpManager: DumpManager,
longPressEffectProvider: Provider<QSLongPressEffect>,
+ mediaCarouselInteractor: MediaCarouselInteractor,
) :
QuickQSPanelController(
view,
@@ -177,6 +184,7 @@
dumpManager,
ResourcesSplitShadeStateController(),
longPressEffectProvider,
+ mediaCarouselInteractor
) {
private var rotation = RotationUtils.ROTATION_NONE
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
index 0ec8552..42b81de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
@@ -116,6 +116,7 @@
"test_spec:\n" +
" QSTileState(" +
"icon=() -> com.android.systemui.common.shared.model.Icon?, " +
+ "iconRes=null, " +
"label=test_data, " +
"activationState=INACTIVE, " +
"secondaryLabel=null, " +
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
index 4215b8c..e7bde681 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
@@ -109,15 +109,10 @@
activationState: QSTileState.ActivationState,
): QSTileState {
val label = testLabel
+ val iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status
return QSTileState(
- icon = {
- Icon.Loaded(
- context.getDrawable(
- com.android.internal.R.drawable.stat_sys_managed_profile_status
- )!!,
- null
- )
- },
+ icon = { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ iconRes = iconRes,
label = label,
activationState = activationState,
secondaryLabel =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index deecc5b..0d7a9e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -146,7 +146,8 @@
@Test
public void testLogStartPartialRecording() {
- MediaProjectionCaptureTarget target = new MediaProjectionCaptureTarget(new LaunchCookie());
+ MediaProjectionCaptureTarget target =
+ new MediaProjectionCaptureTarget(new LaunchCookie(), 12345);
Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false, target);
mRecordingService.onStartCommand(startIntent, 0, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
index 896c3bf..6f5c56e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
@@ -21,41 +21,38 @@
import android.os.Process
import android.os.UserHandle
import android.testing.AndroidTestingRunner
-import android.view.accessibility.AccessibilityManager
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
-import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import java.util.UUID
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
-import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
import org.mockito.kotlin.never
+import org.mockito.kotlin.times
import org.mockito.kotlin.verify
@RunWith(AndroidTestingRunner::class)
@SmallTest
class DefaultScreenshotActionsProviderTest : SysuiTestCase() {
private val actionExecutor = mock<ActionExecutor>()
- private val accessibilityManager = mock<AccessibilityManager>()
private val uiEventLogger = mock<UiEventLogger>()
+ private val actionsCallback = mock<ScreenshotActionsController.ActionsCallback>()
private val request = ScreenshotData.forTesting()
private val validResult = ScreenshotSavedResult(Uri.EMPTY, Process.myUserHandle(), 0)
- private lateinit var viewModel: ScreenshotViewModel
private lateinit var actionsProvider: ScreenshotActionsProvider
@Before
fun setUp() {
- viewModel = ScreenshotViewModel(accessibilityManager)
request.userHandle = UserHandle.OWNER
}
@@ -63,8 +60,9 @@
fun previewActionAccessed_beforeScreenshotCompleted_doesNothing() {
actionsProvider = createActionsProvider()
- assertNotNull(viewModel.previewAction.value)
- viewModel.previewAction.value!!.invoke()
+ val previewActionCaptor = argumentCaptor<() -> Unit>()
+ verify(actionsCallback).providePreviewAction(previewActionCaptor.capture())
+ previewActionCaptor.firstValue.invoke()
verifyNoMoreInteractions(actionExecutor)
}
@@ -72,13 +70,13 @@
fun actionButtonsAccessed_beforeScreenshotCompleted_doesNothing() {
actionsProvider = createActionsProvider()
- assertThat(viewModel.actions.value.size).isEqualTo(2)
- val firstAction = viewModel.actions.value[0]
- assertThat(firstAction.onClicked).isNotNull()
- val secondAction = viewModel.actions.value[1]
- assertThat(secondAction.onClicked).isNotNull()
- firstAction.onClicked!!.invoke()
- secondAction.onClicked!!.invoke()
+ val actionButtonCaptor = argumentCaptor<() -> Unit>()
+ verify(actionsCallback, times(2))
+ .provideActionButton(any(), any(), actionButtonCaptor.capture())
+ val firstAction = actionButtonCaptor.firstValue
+ val secondAction = actionButtonCaptor.secondValue
+ firstAction.invoke()
+ secondAction.invoke()
verifyNoMoreInteractions(actionExecutor)
}
@@ -87,29 +85,39 @@
actionsProvider = createActionsProvider()
actionsProvider.setCompletedScreenshot(validResult)
- viewModel.actions.value[0].onClicked!!.invoke()
- verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED), eq(0), eq(""))
+ val actionButtonCaptor = argumentCaptor<() -> Unit>()
+ verify(actionsCallback, times(2))
+ .provideActionButton(any(), any(), actionButtonCaptor.capture())
+ actionButtonCaptor.firstValue.invoke()
+
+ verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED), eq(0), eq(""))
val intentCaptor = argumentCaptor<Intent>()
verify(actionExecutor)
- .startSharedTransition(capture(intentCaptor), eq(Process.myUserHandle()), eq(true))
- assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_EDIT)
+ .startSharedTransition(intentCaptor.capture(), eq(Process.myUserHandle()), eq(false))
+ assertThat(intentCaptor.firstValue.action).isEqualTo(Intent.ACTION_CHOOSER)
}
@Test
fun actionAccessed_whilePending_launchesMostRecentAction() = runTest {
actionsProvider = createActionsProvider()
- viewModel.actions.value[0].onClicked!!.invoke()
- viewModel.previewAction.value!!.invoke()
- viewModel.actions.value[1].onClicked!!.invoke()
+ val previewActionCaptor = argumentCaptor<() -> Unit>()
+ verify(actionsCallback).providePreviewAction(previewActionCaptor.capture())
+ val actionButtonCaptor = argumentCaptor<() -> Unit>()
+ verify(actionsCallback, times(2))
+ .provideActionButton(any(), any(), actionButtonCaptor.capture())
+
+ actionButtonCaptor.firstValue.invoke()
+ previewActionCaptor.firstValue.invoke()
+ actionButtonCaptor.secondValue.invoke()
actionsProvider.setCompletedScreenshot(validResult)
- verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED), eq(0), eq(""))
+ verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED), eq(0), eq(""))
val intentCaptor = argumentCaptor<Intent>()
verify(actionExecutor)
- .startSharedTransition(capture(intentCaptor), eq(Process.myUserHandle()), eq(false))
- assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_CHOOSER)
+ .startSharedTransition(intentCaptor.capture(), eq(Process.myUserHandle()), eq(true))
+ assertThat(intentCaptor.firstValue.action).isEqualTo(Intent.ACTION_EDIT)
}
@Test
@@ -117,9 +125,12 @@
actionsProvider = createActionsProvider()
val onScrollClick = mock<Runnable>()
- val numActions = viewModel.actions.value.size
actionsProvider.onScrollChipReady(onScrollClick)
- viewModel.actions.value[numActions].onClicked!!.invoke()
+ val actionButtonCaptor = argumentCaptor<() -> Unit>()
+ // share, edit, scroll
+ verify(actionsCallback, times(3))
+ .provideActionButton(any(), any(), actionButtonCaptor.capture())
+ actionButtonCaptor.thirdValue.invoke()
verify(onScrollClick).run()
}
@@ -129,10 +140,13 @@
actionsProvider = createActionsProvider()
val onScrollClick = mock<Runnable>()
- val numActions = viewModel.actions.value.size
actionsProvider.onScrollChipReady(onScrollClick)
+ val actionButtonCaptor = argumentCaptor<() -> Unit>()
actionsProvider.onScrollChipInvalidated()
- viewModel.actions.value[numActions].onClicked!!.invoke()
+ // share, edit, scroll
+ verify(actionsCallback, times(3))
+ .provideActionButton(any(), any(), actionButtonCaptor.capture())
+ actionButtonCaptor.thirdValue.invoke()
verify(onScrollClick, never()).run()
}
@@ -143,11 +157,15 @@
val onScrollClick = mock<Runnable>()
val onScrollClick2 = mock<Runnable>()
- val numActions = viewModel.actions.value.size
+
actionsProvider.onScrollChipReady(onScrollClick)
actionsProvider.onScrollChipInvalidated()
actionsProvider.onScrollChipReady(onScrollClick2)
- viewModel.actions.value[numActions].onClicked!!.invoke()
+ val actionButtonCaptor = argumentCaptor<() -> Unit>()
+ // share, edit, scroll
+ verify(actionsCallback, times(3))
+ .provideActionButton(any(), any(), actionButtonCaptor.capture())
+ actionButtonCaptor.thirdValue.invoke()
verify(onScrollClick2).run()
verify(onScrollClick, never()).run()
@@ -156,11 +174,11 @@
private fun createActionsProvider(): ScreenshotActionsProvider {
return DefaultScreenshotActionsProvider(
context,
- viewModel,
uiEventLogger,
+ UUID.randomUUID(),
request,
- "testid",
- actionExecutor
+ actionExecutor,
+ actionsCallback,
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt
new file mode 100644
index 0000000..2a3c31a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.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.screenshot
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
+import java.util.UUID
+import kotlin.test.Test
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ScreenshotActionsControllerTest : SysuiTestCase() {
+ private val screenshotData = mock<ScreenshotData>()
+ private val actionExecutor = mock<ActionExecutor>()
+ private val viewModel = mock<ScreenshotViewModel>()
+ private val onClick = mock<() -> Unit>()
+
+ private lateinit var actionsController: ScreenshotActionsController
+ private lateinit var fakeActionsProvider1: FakeActionsProvider
+ private lateinit var fakeActionsProvider2: FakeActionsProvider
+ private val actionsProviderFactory =
+ object : ScreenshotActionsProvider.Factory {
+ var isFirstCall = true
+ override fun create(
+ requestId: UUID,
+ request: ScreenshotData,
+ actionExecutor: ActionExecutor,
+ actionsCallback: ScreenshotActionsController.ActionsCallback
+ ): ScreenshotActionsProvider {
+ return if (isFirstCall) {
+ isFirstCall = false
+ fakeActionsProvider1 = FakeActionsProvider(actionsCallback)
+ fakeActionsProvider1
+ } else {
+ fakeActionsProvider2 = FakeActionsProvider(actionsCallback)
+ fakeActionsProvider2
+ }
+ }
+ }
+
+ @Before
+ fun setUp() {
+ actionsController =
+ ScreenshotActionsController(viewModel, actionsProviderFactory, actionExecutor)
+ }
+
+ @Test
+ fun setPreview_onCurrentScreenshot_updatesViewModel() {
+ actionsController.setCurrentScreenshot(screenshotData)
+ fakeActionsProvider1.callPreview(onClick)
+
+ verify(viewModel).setPreviewAction(onClick)
+ }
+
+ @Test
+ fun setPreview_onNonCurrentScreenshot_doesNotUpdateViewModel() {
+ actionsController.setCurrentScreenshot(screenshotData)
+ actionsController.setCurrentScreenshot(screenshotData)
+ fakeActionsProvider1.callPreview(onClick)
+
+ verify(viewModel, never()).setPreviewAction(any())
+ }
+
+ class FakeActionsProvider(
+ private val actionsCallback: ScreenshotActionsController.ActionsCallback
+ ) : ScreenshotActionsProvider {
+
+ fun callPreview(onClick: () -> Unit) {
+ actionsCallback.providePreviewAction(onClick)
+ }
+
+ override fun onScrollChipReady(onClick: Runnable) {}
+
+ override fun onScrollChipInvalidated() {}
+
+ override fun setCompletedScreenshot(result: ScreenshotSavedResult) {}
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 49a467e..1ef44f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -35,7 +35,7 @@
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
import com.android.systemui.communal.data.repository.fakeCommunalRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
@@ -92,7 +92,7 @@
private lateinit var containerView: View
private lateinit var testableLooper: TestableLooper
- private lateinit var communalRepository: FakeCommunalRepository
+ private lateinit var communalRepository: FakeCommunalSceneRepository
private lateinit var underTest: GlanceableHubContainerController
@Before
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 4c2d908..5363c57 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
@@ -46,6 +46,7 @@
import androidx.test.filters.SmallTest;
import com.android.keyguard.CarrierTextManager;
+import com.android.systemui.log.core.FakeLogBuffer;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.connectivity.IconState;
import com.android.systemui.statusbar.connectivity.MobileDataIndicators;
@@ -169,6 +170,7 @@
mActivityStarter,
handler,
TestableLooper.get(this).getLooper(),
+ new ShadeCarrierGroupControllerLogger(FakeLogBuffer.Factory.Companion.create()),
mNetworkController,
mCarrierTextControllerBuilder,
mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
index fe066ca2..59678a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
@@ -20,14 +20,19 @@
import static com.android.systemui.flags.Flags.KEYGUARD_TALKBACK_FIX;
import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRANSIENT;
import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -65,6 +70,8 @@
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.deviceentry.domain.interactor.BiometricMessageInteractor;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor;
import com.android.systemui.dock.DockManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.KeyguardIndication;
@@ -150,6 +157,10 @@
@Mock
protected BiometricMessageInteractor mBiometricMessageInteractor;
@Mock
+ protected DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
+ @Mock
+ protected DeviceEntryFingerprintAuthInteractor mDeviceEntryFingerprintAuthInteractor;
+ @Mock
protected ScreenLifecycle mScreenLifecycle;
@Mock
protected AuthController mAuthController;
@@ -237,6 +248,7 @@
.thenReturn(mock(StateFlow.class));
when(mFaceHelpMessageDeferralFactory.create()).thenReturn(mFaceHelpMessageDeferral);
+ when(mDeviceEntryFingerprintAuthInteractor.isEngaged()).thenReturn(mock(StateFlow.class));
mIndicationHelper = new IndicationHelper(mKeyguardUpdateMonitor);
@@ -279,7 +291,9 @@
mFlags,
mIndicationHelper,
KeyguardInteractorFactory.create(mFlags).getKeyguardInteractor(),
- mBiometricMessageInteractor
+ mBiometricMessageInteractor,
+ mDeviceEntryFingerprintAuthInteractor,
+ mDeviceEntryFaceAuthInteractor
);
mController.init();
mController.setIndicationArea(mIndicationArea);
@@ -306,4 +320,22 @@
mExecutor.runAllReady();
reset(mRotateTextViewController);
}
+
+ void verifyNoMessage(int type) {
+ if (type == INDICATION_TYPE_TRANSIENT) {
+ verify(mRotateTextViewController, never()).showTransient(anyString());
+ } else {
+ verify(mRotateTextViewController, never()).updateIndication(eq(type),
+ any(KeyguardIndication.class), anyBoolean());
+ }
+ }
+
+ void verifyIndicationShown(int indicationType, String message) {
+ verify(mRotateTextViewController)
+ .updateIndication(eq(indicationType),
+ mKeyguardIndicationCaptor.capture(),
+ eq(true));
+ assertThat(mKeyguardIndicationCaptor.getValue().getMessage().toString())
+ .isEqualTo(message);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index cfe9e2a..80011dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -1514,19 +1514,6 @@
}
@Test
- public void onTrustAgentErrorMessageDroppedBecauseFingerprintMessageShowing() {
- createController();
- mController.setVisible(true);
- mController.getKeyguardCallback().onBiometricHelp(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
- "fp not recognized", BiometricSourceType.FINGERPRINT);
- clearInvocations(mRotateTextViewController);
-
- mKeyguardUpdateMonitorCallback.onTrustAgentErrorMessage("testMessage");
- verifyNoMessage(INDICATION_TYPE_TRUST);
- verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
- }
-
- @Test
public void trustGrantedMessageShowsEvenWhenFingerprintMessageShowing() {
createController();
mController.setVisible(true);
@@ -1591,24 +1578,6 @@
verify(mRotateTextViewController).showTransient(eq(message));
}
- private void verifyNoMessage(int type) {
- if (type == INDICATION_TYPE_TRANSIENT) {
- verify(mRotateTextViewController, never()).showTransient(anyString());
- } else {
- verify(mRotateTextViewController, never()).updateIndication(eq(type),
- anyObject(), anyBoolean());
- }
- }
-
- private void verifyIndicationShown(int indicationType, String message) {
- verify(mRotateTextViewController)
- .updateIndication(eq(indicationType),
- mKeyguardIndicationCaptor.capture(),
- eq(true));
- assertThat(mKeyguardIndicationCaptor.getValue().getMessage().toString())
- .isEqualTo(message);
- }
-
private void fingerprintUnlockIsNotPossible() {
setupFingerprintUnlockPossible(false);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt
index 4a14f88..a68ba06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt
@@ -21,12 +21,17 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
+import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE
+import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP
+import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
@SmallTest
@@ -62,6 +67,33 @@
verify(mIndicationArea, times(3)).visibility = View.VISIBLE
}
+ @Test
+ fun onTrustAgentErrorMessageDelayed_fingerprintEngaged() {
+ createController()
+ mController.setVisible(true)
+
+ // GIVEN fingerprint is engaged
+ whenever(mDeviceEntryFingerprintAuthInteractor.isEngaged).thenReturn(MutableStateFlow(true))
+
+ // WHEN a trust agent error message arrives
+ mKeyguardUpdateMonitorCallback.onTrustAgentErrorMessage("testMessage")
+ mExecutor.runAllReady()
+
+ // THEN no message shows immediately since fingerprint is engaged
+ verifyNoMessage(INDICATION_TYPE_TRUST)
+ verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE)
+ verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP)
+
+ // WHEN fingerprint is no longer engaged
+ whenever(mDeviceEntryFingerprintAuthInteractor.isEngaged)
+ .thenReturn(MutableStateFlow(false))
+ mController.mIsFingerprintEngagedCallback.accept(false)
+ mExecutor.runAllReady()
+
+ // THEN the message will show
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "testMessage")
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt
new file mode 100644
index 0000000..25efaf1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.chips.screenrecord.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.screenrecord.data.model.ScreenRecordModel
+import com.android.systemui.screenrecord.data.repository.screenRecordRepository
+import com.android.systemui.statusbar.chips.domain.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.viewmodel.screenRecordChipInteractor
+import com.android.systemui.util.time.fakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+
+@SmallTest
+class ScreenRecordChipInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private val screenRecordRepo = kosmos.screenRecordRepository
+ private val systemClock = kosmos.fakeSystemClock
+
+ private val underTest = kosmos.screenRecordChipInteractor
+
+ @Test
+ fun chip_doingNothingState_isHidden() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ screenRecordRepo.screenRecordState.value = ScreenRecordModel.DoingNothing
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+ }
+
+ @Test
+ fun chip_startingState_isHidden() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(400)
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+ }
+
+ @Test
+ fun chip_recordingState_isShownWithIcon() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ val icon = (latest as OngoingActivityChipModel.Shown).icon
+ assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.stat_sys_screen_record)
+ }
+
+ @Test
+ fun chip_timeResetsOnEachNewRecording() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ systemClock.setElapsedRealtime(1234)
+ screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(1234)
+
+ screenRecordRepo.screenRecordState.value = ScreenRecordModel.DoingNothing
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+
+ systemClock.setElapsedRealtime(5678)
+ screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(5678)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index fa2b343..1260f07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -24,7 +24,9 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.screenrecord.data.model.ScreenRecordModel
+import com.android.systemui.screenrecord.data.repository.screenRecordRepository
+import com.android.systemui.statusbar.chips.domain.model.OngoingActivityChipModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -38,7 +40,7 @@
@Test
fun chip_allHidden_hidden() =
kosmos.testScope.runTest {
- kosmos.screenRecordChipInteractor.chip.value = OngoingActivityChipModel.Hidden
+ kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.DoingNothing
kosmos.callChipInteractor.chip.value = OngoingActivityChipModel.Hidden
val latest by collectLastValue(underTest.chip)
@@ -49,28 +51,18 @@
@Test
fun chip_screenRecordShow_restHidden_screenRecordShown() =
kosmos.testScope.runTest {
- val screenRecordChip =
- OngoingActivityChipModel.Shown(
- Icon.Resource(R.drawable.ic_cake, ContentDescription.Loaded("icon")),
- startTimeMs = 500L,
- ) {}
- kosmos.screenRecordChipInteractor.chip.value = screenRecordChip
+ kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
kosmos.callChipInteractor.chip.value = OngoingActivityChipModel.Hidden
val latest by collectLastValue(underTest.chip)
- assertThat(latest).isEqualTo(screenRecordChip)
+ assertIsScreenRecordChip(latest)
}
@Test
fun chip_screenRecordShowAndCallShow_screenRecordShown() =
kosmos.testScope.runTest {
- val screenRecordChip =
- OngoingActivityChipModel.Shown(
- Icon.Resource(R.drawable.ic_cake, ContentDescription.Loaded("icon")),
- startTimeMs = 500L,
- ) {}
- kosmos.screenRecordChipInteractor.chip.value = screenRecordChip
+ kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
val callChip =
OngoingActivityChipModel.Shown(
@@ -81,13 +73,13 @@
val latest by collectLastValue(underTest.chip)
- assertThat(latest).isEqualTo(screenRecordChip)
+ assertIsScreenRecordChip(latest)
}
@Test
fun chip_screenRecordHideAndCallShown_callShown() =
kosmos.testScope.runTest {
- kosmos.screenRecordChipInteractor.chip.value = OngoingActivityChipModel.Hidden
+ kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.DoingNothing
val callChip =
OngoingActivityChipModel.Shown(
@@ -111,34 +103,24 @@
startTimeMs = 600L,
) {}
kosmos.callChipInteractor.chip.value = callChip
- kosmos.screenRecordChipInteractor.chip.value = OngoingActivityChipModel.Hidden
+ kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.DoingNothing
val latest by collectLastValue(underTest.chip)
assertThat(latest).isEqualTo(callChip)
// WHEN the higher priority screen record chip is added
- val screenRecordChip =
- OngoingActivityChipModel.Shown(
- Icon.Resource(R.drawable.ic_cake, ContentDescription.Loaded("icon")),
- startTimeMs = 500L,
- ) {}
- kosmos.screenRecordChipInteractor.chip.value = screenRecordChip
+ kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
// THEN the higher priority screen record chip is used
- assertThat(latest).isEqualTo(screenRecordChip)
+ assertIsScreenRecordChip(latest)
}
@Test
fun chip_highestPriorityChipRemoved_showsNextPriorityChip() =
kosmos.testScope.runTest {
// Start with both the higher priority screen record chip and lower priority call chip
- val screenRecordChip =
- OngoingActivityChipModel.Shown(
- Icon.Resource(R.drawable.ic_cake, ContentDescription.Loaded("icon")),
- startTimeMs = 500L,
- ) {}
- kosmos.screenRecordChipInteractor.chip.value = screenRecordChip
+ kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
val callChip =
OngoingActivityChipModel.Shown(
@@ -149,12 +131,18 @@
val latest by collectLastValue(underTest.chip)
- assertThat(latest).isEqualTo(screenRecordChip)
+ assertIsScreenRecordChip(latest)
// WHEN the higher priority screen record is removed
- kosmos.screenRecordChipInteractor.chip.value = OngoingActivityChipModel.Hidden
+ kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.DoingNothing
// THEN the lower priority call is used
assertThat(latest).isEqualTo(callChip)
}
+
+ private fun assertIsScreenRecordChip(latest: OngoingActivityChipModel?) {
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ val icon = (latest as OngoingActivityChipModel.Shown).icon
+ assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.stat_sys_screen_record)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
index 0906d8e..9f752a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
@@ -22,7 +22,7 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.AnimatorTestRule
-import com.android.systemui.communal.data.repository.communalRepository
+import com.android.systemui.communal.data.repository.communalSceneRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dump.DumpManager
@@ -181,7 +181,7 @@
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(CommunalScenes.Communal)
)
- kosmos.communalRepository.setTransitionState(transitionState)
+ kosmos.communalSceneRepository.setTransitionState(transitionState)
runCurrent()
setDozeAmount(0f)
verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
@@ -195,7 +195,7 @@
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(CommunalScenes.Communal)
)
- kosmos.communalRepository.setTransitionState(transitionState)
+ kosmos.communalSceneRepository.setTransitionState(transitionState)
runCurrent()
setDozeAmount(0f)
verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 3e8461a..bfe5c6e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -58,7 +58,6 @@
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Handler;
import android.os.PowerManager;
-import android.os.RemoteException;
import android.platform.test.annotations.DisableFlags;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -81,6 +80,7 @@
import com.android.systemui.util.FakeEventLog;
import com.android.systemui.util.settings.FakeGlobalSettings;
import com.android.systemui.util.time.FakeSystemClock;
+import com.android.wm.shell.bubbles.Bubbles;
import org.junit.Before;
import org.junit.Test;
@@ -90,6 +90,7 @@
import java.util.Arrays;
import java.util.HashSet;
+import java.util.Optional;
import java.util.Set;
/**
@@ -127,6 +128,8 @@
UserTracker mUserTracker;
@Mock
DeviceProvisionedController mDeviceProvisionedController;
+ @Mock
+ Bubbles mBubbles;
FakeSystemClock mSystemClock;
FakeGlobalSettings mGlobalSettings;
FakeEventLog mEventLog;
@@ -137,6 +140,7 @@
public void setup() {
MockitoAnnotations.initMocks(this);
when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
+ when(mBubbles.canShowBubbleNotification()).thenReturn(true);
mUiEventLoggerFake = new UiEventLoggerFake();
mSystemClock = new FakeSystemClock();
@@ -161,7 +165,8 @@
mDeviceProvisionedController,
mSystemClock,
mGlobalSettings,
- mEventLog);
+ mEventLog,
+ Optional.of(mBubbles));
mNotifInterruptionStateProvider.mUseHeadsUp = true;
}
@@ -170,7 +175,7 @@
* {@link NotificationInterruptStateProviderImpl#shouldHeadsUp(NotificationEntry)} will
* pass as long its provided NotificationEntry fulfills importance & DND checks.
*/
- private void ensureStateForHeadsUpWhenAwake() throws RemoteException {
+ private void ensureStateForHeadsUpWhenAwake() {
when(mHeadsUpManager.isSnoozed(any())).thenReturn(false);
when(mStatusBarStateController.isDozing()).thenReturn(false);
@@ -208,7 +213,7 @@
}
@Test
- public void testShouldHeadsUpAwake() throws RemoteException {
+ public void testShouldHeadsUpAwake() {
ensureStateForHeadsUpWhenAwake();
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
@@ -216,7 +221,7 @@
}
@Test
- public void testShouldNotHeadsUp_suppressedForGroups() throws RemoteException {
+ public void testShouldNotHeadsUp_suppressedForGroups() {
// GIVEN state for "heads up when awake" is true
ensureStateForHeadsUpWhenAwake();
@@ -315,7 +320,7 @@
}
@Test
- public void testShouldHeadsUp() throws RemoteException {
+ public void testShouldHeadsUp() {
ensureStateForHeadsUpWhenAwake();
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
@@ -327,7 +332,7 @@
* the bubble is shown rather than the heads up.
*/
@Test
- public void testShouldNotHeadsUp_bubble() throws RemoteException {
+ public void testShouldNotHeadsUp_bubble() {
ensureStateForHeadsUpWhenAwake();
// Bubble bit only applies to interruption when we're in the shade
@@ -337,10 +342,26 @@
}
/**
+ * If the notification is a bubble, and the user is not on AOD / lockscreen, but a bubble
+ * notification can't be shown, then show the heads up.
+ */
+ @Test
+ public void testShouldHeadsUp_bubble_bubblesCannotShowNotification() {
+ ensureStateForHeadsUpWhenAwake();
+
+ // Bubble bit only applies to interruption when we're in the shade
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+ when(mBubbles.canShowBubbleNotification()).thenReturn(false);
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(createBubble())).isTrue();
+ }
+
+ /**
* If we're not allowed to alert in general, we shouldn't be shown as heads up.
*/
@Test
- public void testShouldNotHeadsUp_filtered() throws RemoteException {
+ public void testShouldNotHeadsUp_filtered() {
ensureStateForHeadsUpWhenAwake();
// Make canAlertCommon false by saying it's filtered out
when(mKeyguardNotificationVisibilityProvider.shouldHideNotification(any()))
@@ -355,7 +376,7 @@
* {@link android.app.NotificationManager.Policy#SUPPRESSED_EFFECT_PEEK}.
*/
@Test
- public void testShouldNotHeadsUp_suppressPeek() throws RemoteException {
+ public void testShouldNotHeadsUp_suppressPeek() {
ensureStateForHeadsUpWhenAwake();
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
@@ -371,7 +392,7 @@
* to show as a heads up.
*/
@Test
- public void testShouldNotHeadsUp_lessImportant() throws RemoteException {
+ public void testShouldNotHeadsUp_lessImportant() {
ensureStateForHeadsUpWhenAwake();
NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
@@ -382,7 +403,7 @@
* If the device is not in use then we shouldn't be shown as heads up.
*/
@Test
- public void testShouldNotHeadsUp_deviceNotInUse() throws RemoteException {
+ public void testShouldNotHeadsUp_deviceNotInUse() {
ensureStateForHeadsUpWhenAwake();
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
@@ -397,7 +418,7 @@
}
@Test
- public void testShouldNotHeadsUp_headsUpSuppressed() throws RemoteException {
+ public void testShouldNotHeadsUp_headsUpSuppressed() {
ensureStateForHeadsUpWhenAwake();
// If a suppressor is suppressing heads up, then it shouldn't be shown as a heads up.
@@ -408,7 +429,7 @@
}
@Test
- public void testShouldNotHeadsUpAwake_awakeInterruptsSuppressed() throws RemoteException {
+ public void testShouldNotHeadsUpAwake_awakeInterruptsSuppressed() {
ensureStateForHeadsUpWhenAwake();
// If a suppressor is suppressing heads up, then it shouldn't be shown as a heads up.
@@ -446,7 +467,7 @@
}
@Test
- public void testShouldHeadsUp_oldWhen_whenNow() throws Exception {
+ public void testShouldHeadsUp_oldWhen_whenNow() {
ensureStateForHeadsUpWhenAwake();
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
@@ -458,7 +479,7 @@
}
@Test
- public void testShouldHeadsUp_oldWhen_whenRecent() throws Exception {
+ public void testShouldHeadsUp_oldWhen_whenRecent() {
ensureStateForHeadsUpWhenAwake();
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
@@ -472,7 +493,7 @@
@Test
@DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME)
- public void testShouldHeadsUp_oldWhen_whenZero() throws Exception {
+ public void testShouldHeadsUp_oldWhen_whenZero() {
ensureStateForHeadsUpWhenAwake();
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
@@ -486,7 +507,7 @@
}
@Test
- public void testShouldHeadsUp_oldWhen_whenNegative() throws Exception {
+ public void testShouldHeadsUp_oldWhen_whenNegative() {
ensureStateForHeadsUpWhenAwake();
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
@@ -499,7 +520,7 @@
}
@Test
- public void testShouldHeadsUp_oldWhen_hasFullScreenIntent() throws Exception {
+ public void testShouldHeadsUp_oldWhen_hasFullScreenIntent() {
ensureStateForHeadsUpWhenAwake();
long when = makeWhenHoursAgo(25);
@@ -514,7 +535,7 @@
}
@Test
- public void testShouldHeadsUp_oldWhen_isForegroundService() throws Exception {
+ public void testShouldHeadsUp_oldWhen_isForegroundService() {
ensureStateForHeadsUpWhenAwake();
long when = makeWhenHoursAgo(25);
@@ -529,7 +550,7 @@
}
@Test
- public void testShouldNotHeadsUp_oldWhen() throws Exception {
+ public void testShouldNotHeadsUp_oldWhen() {
ensureStateForHeadsUpWhenAwake();
long when = makeWhenHoursAgo(25);
@@ -543,7 +564,7 @@
}
@Test
- public void testShouldNotFullScreen_notPendingIntent() throws RemoteException {
+ public void testShouldNotFullScreen_notPendingIntent() {
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
when(mPowerManager.isInteractive()).thenReturn(true);
when(mStatusBarStateController.isDreaming()).thenReturn(false);
@@ -559,7 +580,7 @@
}
@Test
- public void testShouldNotFullScreen_suppressedOnlyByDND() throws RemoteException {
+ public void testShouldNotFullScreen_suppressedOnlyByDND() {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
modifyRanking(entry)
.setSuppressedVisualEffects(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT)
@@ -578,7 +599,7 @@
}
@Test
- public void testShouldNotFullScreen_suppressedByDNDAndOther() throws RemoteException {
+ public void testShouldNotFullScreen_suppressedByDNDAndOther() {
NotificationEntry entry = createFsiNotification(IMPORTANCE_LOW, /* silenced */ false);
modifyRanking(entry)
.setSuppressedVisualEffects(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT)
@@ -597,7 +618,7 @@
}
@Test
- public void testShouldNotFullScreen_notHighImportance() throws RemoteException {
+ public void testShouldNotFullScreen_notHighImportance() {
NotificationEntry entry = createFsiNotification(IMPORTANCE_DEFAULT, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
when(mStatusBarStateController.isDreaming()).thenReturn(false);
@@ -613,7 +634,7 @@
}
@Test
- public void testShouldNotFullScreen_isGroupAlertSilenced() throws RemoteException {
+ public void testShouldNotFullScreen_isGroupAlertSilenced() {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ true);
when(mPowerManager.isInteractive()).thenReturn(false);
when(mStatusBarStateController.isDreaming()).thenReturn(true);
@@ -664,7 +685,7 @@
}
@Test
- public void testShouldFullScreen_notInteractive() throws RemoteException {
+ public void testShouldFullScreen_notInteractive() {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder("foo")
.setSuppressNotification(false).build();
@@ -683,7 +704,7 @@
}
@Test
- public void testShouldFullScreen_isDreaming() throws RemoteException {
+ public void testShouldFullScreen_isDreaming() {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
when(mStatusBarStateController.isDreaming()).thenReturn(true);
@@ -699,7 +720,7 @@
}
@Test
- public void testShouldFullScreen_onKeyguard() throws RemoteException {
+ public void testShouldFullScreen_onKeyguard() {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
when(mStatusBarStateController.isDreaming()).thenReturn(false);
@@ -734,7 +755,7 @@
}
@Test
- public void testShouldNotFullScreen_willHun() throws RemoteException {
+ public void testShouldNotFullScreen_willHun() {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
when(mPowerManager.isScreenOn()).thenReturn(true);
@@ -751,7 +772,7 @@
}
@Test
- public void testShouldNotFullScreen_snoozed_occluding() throws Exception {
+ public void testShouldNotFullScreen_snoozed_occluding() {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
when(mPowerManager.isScreenOn()).thenReturn(true);
@@ -771,7 +792,7 @@
}
@Test
- public void testShouldHeadsUp_snoozed_occluding() throws Exception {
+ public void testShouldHeadsUp_snoozed_occluding() {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
when(mPowerManager.isScreenOn()).thenReturn(true);
@@ -795,7 +816,7 @@
}
@Test
- public void testShouldNotFullScreen_snoozed_lockedShade() throws Exception {
+ public void testShouldNotFullScreen_snoozed_lockedShade() {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
when(mPowerManager.isScreenOn()).thenReturn(true);
@@ -815,7 +836,7 @@
}
@Test
- public void testShouldHeadsUp_snoozed_lockedShade() throws Exception {
+ public void testShouldHeadsUp_snoozed_lockedShade() {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
when(mPowerManager.isScreenOn()).thenReturn(true);
@@ -839,7 +860,7 @@
}
@Test
- public void testShouldNotFullScreen_snoozed_unlocked() throws Exception {
+ public void testShouldNotFullScreen_snoozed_unlocked() {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
when(mPowerManager.isScreenOn()).thenReturn(true);
@@ -859,7 +880,7 @@
}
@Test
- public void testShouldNotScreen_appSuspended() throws RemoteException {
+ public void testShouldNotScreen_appSuspended() {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(false);
when(mStatusBarStateController.isDreaming()).thenReturn(false);
@@ -902,7 +923,7 @@
}
@Test
- public void testShouldHeadsUp_snoozed_unlocked() throws Exception {
+ public void testShouldHeadsUp_snoozed_unlocked() {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
when(mPowerManager.isScreenOn()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
index a6177e8..6c7a95f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.DecisionImpl
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.FullScreenIntentDecisionImpl
+import java.util.Optional
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -55,7 +56,8 @@
deviceProvisionedController,
systemClock,
globalSettings,
- eventLog
+ eventLog,
+ Optional.of(bubbles)
)
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index eeb51a6..7903a73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
+import java.util.Optional
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.anyString
@@ -56,7 +57,8 @@
userTracker,
avalancheProvider,
systemSettings,
- packageManager
+ packageManager,
+ Optional.of(bubbles)
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index 71e7dc5..a457405 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -75,8 +75,6 @@
import com.android.systemui.statusbar.policy.FakeDeviceProvisionedController
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.util.FakeEventLog
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.FakeGlobalSettings
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.SystemSettings
@@ -85,12 +83,15 @@
import com.android.systemui.utils.leaks.FakeKeyguardStateController
import com.android.systemui.utils.leaks.LeakCheckedTest
import com.android.systemui.utils.os.FakeHandler
+import com.android.wm.shell.bubbles.Bubbles
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
private val fakeLogBuffer =
@@ -129,6 +130,7 @@
protected val uiEventLogger = UiEventLoggerFake()
protected val userTracker = FakeUserTracker()
protected val avalancheProvider: AvalancheProvider = mock()
+ protected val bubbles: Bubbles = mock()
lateinit var systemSettings: SystemSettings
protected val packageManager: PackageManager = mock()
@@ -159,6 +161,7 @@
deviceProvisionedController.currentUser = userId
userTracker.set(listOf(user), /* currentUserIndex = */ 0)
systemSettings = FakeSettings()
+ whenever(bubbles.canShowBubbleNotification()).thenReturn(true)
provider.start()
}
@@ -208,6 +211,14 @@
}
@Test
+ fun testShouldPeek_bubblesCannotShowNotification() {
+ whenever(bubbles.canShowBubbleNotification()).thenReturn(false)
+ ensurePeekState { statusBarState = SHADE }
+ assertShouldHeadsUp(buildPeekEntry { isBubble = true })
+ assertNoEventsLogged()
+ }
+
+ @Test
fun testShouldPeek_isBubble_shadeLocked() {
ensurePeekState { statusBarState = SHADE_LOCKED }
assertShouldHeadsUp(buildPeekEntry { isBubble = true })
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
index 61c008b..01e638b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
@@ -32,6 +32,8 @@
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.SystemSettings
import com.android.systemui.util.time.SystemClock
+import com.android.wm.shell.bubbles.Bubbles
+import java.util.Optional
object VisualInterruptionDecisionProviderTestUtil {
fun createProviderByFlag(
@@ -55,6 +57,7 @@
avalancheProvider: AvalancheProvider,
systemSettings: SystemSettings,
packageManager: PackageManager,
+ bubbles: Optional<Bubbles>,
): VisualInterruptionDecisionProvider {
return if (VisualInterruptionRefactor.isEnabled) {
VisualInterruptionDecisionProviderImpl(
@@ -75,7 +78,8 @@
userTracker,
avalancheProvider,
systemSettings,
- packageManager
+ packageManager,
+ bubbles
)
} else {
NotificationInterruptStateProviderWrapper(
@@ -95,7 +99,8 @@
deviceProvisionedController,
systemClock,
globalSettings,
- eventLog
+ eventLog,
+ bubbles
)
)
}
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 cde241b..62804ed1 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
@@ -172,7 +172,6 @@
import com.android.systemui.statusbar.notification.interruption.AvalancheProvider;
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionLogger;
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProviderTestUtil;
@@ -189,7 +188,6 @@
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
-import com.android.systemui.util.EventLog;
import com.android.systemui.util.FakeEventLog;
import com.android.systemui.util.WallpaperController;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -197,10 +195,8 @@
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.FakeGlobalSettings;
import com.android.systemui.util.settings.FakeSettings;
-import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.settings.SystemSettings;
import com.android.systemui.util.time.FakeSystemClock;
-import com.android.systemui.util.time.SystemClock;
import com.android.systemui.volume.VolumeComponent;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.startingsurface.StartingSurface;
@@ -374,6 +370,8 @@
mFakeGlobalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON);
+ when(mBubbles.canShowBubbleNotification()).thenReturn(true);
+
mVisualInterruptionDecisionProvider =
VisualInterruptionDecisionProviderTestUtil.INSTANCE.createProviderByFlag(
mAmbientDisplayConfiguration,
@@ -395,7 +393,8 @@
mUserTracker,
mAvalancheProvider,
mSystemSettings,
- mPackageManager);
+ mPackageManager,
+ Optional.of(mBubbles));
mVisualInterruptionDecisionProvider.start();
mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
@@ -1418,46 +1417,4 @@
verify(mStatusBarStateController).addCallback(callbackCaptor.capture(), anyInt());
callbackCaptor.getValue().onDozingChanged(isDozing);
}
-
- public static class TestableNotificationInterruptStateProviderImpl extends
- NotificationInterruptStateProviderImpl {
-
- TestableNotificationInterruptStateProviderImpl(
- PowerManager powerManager,
- AmbientDisplayConfiguration ambientDisplayConfiguration,
- StatusBarStateController controller,
- KeyguardStateController keyguardStateController,
- BatteryController batteryController,
- HeadsUpManager headsUpManager,
- NotificationInterruptLogger logger,
- Handler mainHandler,
- NotifPipelineFlags flags,
- KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
- UiEventLogger uiEventLogger,
- UserTracker userTracker,
- DeviceProvisionedController deviceProvisionedController,
- SystemClock systemClock,
- GlobalSettings globalSettings,
- EventLog eventLog) {
- super(
- powerManager,
- ambientDisplayConfiguration,
- batteryController,
- controller,
- keyguardStateController,
- headsUpManager,
- logger,
- mainHandler,
- flags,
- keyguardNotificationVisibilityProvider,
- uiEventLogger,
- userTracker,
- deviceProvisionedController,
- systemClock,
- globalSettings,
- eventLog
- );
- mUseHeadsUp = true;
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
index 7ca3b1c..6300953 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.satellite.data
+import android.telephony.TelephonyManager
import android.telephony.satellite.SatelliteManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -50,11 +51,13 @@
private val demoModeController =
mock<DemoModeController>().apply { whenever(this.isInDemoMode).thenReturn(false) }
private val satelliteManager = mock<SatelliteManager>()
+ private val telephonyManager = mock<TelephonyManager>()
private val systemClock = FakeSystemClock()
private val realImpl =
DeviceBasedSatelliteRepositoryImpl(
Optional.of(satelliteManager),
+ telephonyManager,
testDispatcher,
testScope.backgroundScope,
FakeLogBuffer.Factory.create(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index 6b0ad4b..6651676 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -18,6 +18,8 @@
import android.os.OutcomeReceiver
import android.os.Process
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyManager
import android.telephony.satellite.NtnSignalStrength
import android.telephony.satellite.NtnSignalStrengthCallback
import android.telephony.satellite.SatelliteManager
@@ -36,6 +38,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers
import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.MIN_UPTIME
import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.POLLING_INTERVAL_MS
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
@@ -59,6 +62,7 @@
import org.mockito.Mockito
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.never
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -69,6 +73,7 @@
private lateinit var underTest: DeviceBasedSatelliteRepositoryImpl
@Mock private lateinit var satelliteManager: SatelliteManager
+ @Mock private lateinit var telephonyManager: TelephonyManager
private val systemClock = FakeSystemClock()
private val dispatcher = StandardTestDispatcher()
@@ -86,6 +91,7 @@
underTest =
DeviceBasedSatelliteRepositoryImpl(
Optional.empty(),
+ telephonyManager,
dispatcher,
testScope.backgroundScope,
FakeLogBuffer.Factory.create(),
@@ -362,6 +368,68 @@
verify(satelliteManager).registerForModemStateChanged(any(), any())
}
+ @Test
+ fun telephonyCrash_repoReregistersConnectionStateListener() =
+ testScope.runTest {
+ setupDefaultRepo()
+
+ // GIVEN connection state is requested
+ val connectionState by collectLastValue(underTest.connectionState)
+
+ runCurrent()
+
+ val telephonyCallback =
+ MobileTelephonyHelpers.getTelephonyCallbackForType<
+ TelephonyCallback.RadioPowerStateListener
+ >(
+ telephonyManager
+ )
+
+ // THEN listener is registered once
+ verify(satelliteManager, times(1)).registerForModemStateChanged(any(), any())
+
+ // WHEN a crash event happens (detected by radio state change)
+ telephonyCallback.onRadioPowerStateChanged(TelephonyManager.RADIO_POWER_ON)
+ runCurrent()
+ telephonyCallback.onRadioPowerStateChanged(TelephonyManager.RADIO_POWER_OFF)
+ runCurrent()
+
+ // THEN listeners are unregistered and re-registered
+ verify(satelliteManager, times(1)).unregisterForModemStateChanged(any())
+ verify(satelliteManager, times(2)).registerForModemStateChanged(any(), any())
+ }
+
+ @Test
+ fun telephonyCrash_repoReregistersSignalStrengthListener() =
+ testScope.runTest {
+ setupDefaultRepo()
+
+ // GIVEN signal strength is requested
+ val signalStrength by collectLastValue(underTest.signalStrength)
+
+ runCurrent()
+
+ val telephonyCallback =
+ MobileTelephonyHelpers.getTelephonyCallbackForType<
+ TelephonyCallback.RadioPowerStateListener
+ >(
+ telephonyManager
+ )
+
+ // THEN listeners are registered the first time
+ verify(satelliteManager, times(1)).registerForNtnSignalStrengthChanged(any(), any())
+
+ // WHEN a crash event happens (detected by radio state change)
+ telephonyCallback.onRadioPowerStateChanged(TelephonyManager.RADIO_POWER_ON)
+ runCurrent()
+ telephonyCallback.onRadioPowerStateChanged(TelephonyManager.RADIO_POWER_OFF)
+ runCurrent()
+
+ // THEN listeners are unregistered and re-registered
+ verify(satelliteManager, times(1)).unregisterForNtnSignalStrengthChanged(any())
+ verify(satelliteManager, times(2)).registerForNtnSignalStrengthChanged(any(), any())
+ }
+
private fun setUpRepo(
uptime: Long = MIN_UPTIME,
satMan: SatelliteManager? = satelliteManager,
@@ -380,6 +448,7 @@
underTest =
DeviceBasedSatelliteRepositoryImpl(
if (satMan != null) Optional.of(satMan) else Optional.empty(),
+ telephonyManager,
dispatcher,
testScope.backgroundScope,
FakeLogBuffer.Factory.create(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 7b0a556..0f89dcc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -152,6 +152,7 @@
import com.android.systemui.util.settings.FakeGlobalSettings;
import com.android.systemui.util.settings.SystemSettings;
import com.android.systemui.util.time.SystemClock;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.bubbles.Bubble;
@@ -468,7 +469,8 @@
mock(UserTracker.class),
mock(AvalancheProvider.class),
mock(SystemSettings.class),
- mock(PackageManager.class)
+ mock(PackageManager.class),
+ Optional.of(mock(Bubbles.class))
);
interruptionDecisionProvider.start();
@@ -2299,6 +2301,66 @@
assertThat(bubbleStateListener.mStateChangeCalls).isEqualTo(0);
}
+ @EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
+ @Test
+ public void showBubbleOverflow_hasOverflowContents() {
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true);
+ assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
+
+ BubbleStackView stackView = mBubbleController.getStackView();
+ spyOn(stackView);
+
+ // Dismiss the bubble so it's in the overflow
+ mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
+ assertThat(mBubbleData.getOverflowBubbles()).isNotEmpty();
+
+ verify(stackView).showOverflow(eq(true));
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
+ @Test
+ public void showBubbleOverflow_isEmpty() {
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true);
+ assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
+
+ BubbleStackView stackView = mBubbleController.getStackView();
+ spyOn(stackView);
+
+ // Dismiss the bubble so it's in the overflow
+ mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
+ assertThat(mBubbleData.getOverflowBubbles()).isNotEmpty();
+ verify(stackView).showOverflow(eq(true));
+
+ // Cancel the bubble so it's removed from the overflow
+ mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL);
+ assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
+ verify(stackView).showOverflow(eq(false));
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
+ @Test
+ public void showBubbleOverflow_ignored() {
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true);
+ assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
+
+ BubbleStackView stackView = mBubbleController.getStackView();
+ spyOn(stackView);
+
+ // Dismiss the bubble so it's in the overflow
+ mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
+ assertThat(mBubbleData.getOverflowBubbles()).isNotEmpty();
+
+ // Cancel the bubble so it's removed from the overflow
+ mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL);
+ assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
+
+ // Show overflow should never be called if the flag is off
+ verify(stackView, never()).showOverflow(anyBoolean());
+ }
+
/** Creates a bubble using the userId and package. */
private Bubble createBubble(int userId, String pkg) {
final UserHandle userHandle = new UserHandle(userId);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
deleted file mode 100644
index c9964c2..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.wmshell;
-
-import android.hardware.display.AmbientDisplayConfiguration;
-import android.os.Handler;
-import android.os.PowerManager;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.EventLog;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.time.SystemClock;
-
-public class TestableNotificationInterruptStateProviderImpl
- extends NotificationInterruptStateProviderImpl {
-
- TestableNotificationInterruptStateProviderImpl(
- PowerManager powerManager,
- AmbientDisplayConfiguration ambientDisplayConfiguration,
- StatusBarStateController statusBarStateController,
- KeyguardStateController keyguardStateController,
- BatteryController batteryController,
- HeadsUpManager headsUpManager,
- NotificationInterruptLogger logger,
- Handler mainHandler,
- NotifPipelineFlags flags,
- KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
- UiEventLogger uiEventLogger,
- UserTracker userTracker,
- DeviceProvisionedController deviceProvisionedController,
- SystemClock systemClock,
- GlobalSettings globalSettings,
- EventLog eventLog) {
- super(
- powerManager,
- ambientDisplayConfiguration,
- batteryController,
- statusBarStateController,
- keyguardStateController,
- headsUpManager,
- logger,
- mainHandler,
- flags,
- keyguardNotificationVisibilityProvider,
- uiEventLogger,
- userTracker,
- deviceProvisionedController,
- systemClock,
- globalSettings,
- eventLog);
- mUseHeadsUp = true;
- }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
index 070a369..f75cdd4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
@@ -26,15 +26,15 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.statusBarStateController
-import com.android.systemui.statusbar.policy.KeyguardStateControllerImpl
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.statusbar.policy.keyguardStateController
import com.android.systemui.util.time.systemClock
-var Kosmos.alternateBouncerInteractor by
+val Kosmos.alternateBouncerInteractor: AlternateBouncerInteractor by
Kosmos.Fixture {
AlternateBouncerInteractor(
statusBarStateController = statusBarStateController,
- keyguardStateController = mock<KeyguardStateControllerImpl>(),
+ keyguardStateController = keyguardStateController,
bouncerRepository = keyguardBouncerRepository,
fingerprintPropertyRepository = fingerprintPropertyRepository,
biometricSettingsRepository = biometricSettingsRepository,
@@ -44,5 +44,6 @@
keyguardInteractor = { keyguardInteractor },
keyguardTransitionInteractor = { keyguardTransitionInteractor },
scope = testScope.backgroundScope,
+ sceneInteractor = { sceneInteractor },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt
index 482d60c..c7955c3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt
@@ -21,7 +21,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
val Kosmos.fakeCommunalRepository by Fixture {
- FakeCommunalRepository(applicationScope = applicationCoroutineScope)
+ FakeCommunalSceneRepository(applicationScope = applicationCoroutineScope)
}
-val Kosmos.communalRepository by Fixture<CommunalRepository> { fakeCommunalRepository }
+val Kosmos.communalSceneRepository by Fixture<CommunalSceneRepository> { fakeCommunalRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt
similarity index 88%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt
index d958bae..a7bf87d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt
@@ -14,14 +14,17 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
-/** Fake implementation of [CommunalRepository]. */
+/** Fake implementation of [CommunalSceneRepository]. */
@OptIn(ExperimentalCoroutinesApi::class)
-class FakeCommunalRepository(
+class FakeCommunalSceneRepository(
applicationScope: CoroutineScope,
override val currentScene: MutableStateFlow<SceneKey> =
MutableStateFlow(CommunalScenes.Default),
-) : CommunalRepository {
- override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) {
+) : CommunalSceneRepository {
+ override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) =
+ snapToScene(toScene)
+
+ override fun snapToScene(toScene: SceneKey) {
this.currentScene.value = toScene
this._transitionState.value = flowOf(ObservableTransitionState.Idle(toScene))
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 3fe6973..1583d1c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -20,7 +20,6 @@
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.communal.data.repository.communalMediaRepository
import com.android.systemui.communal.data.repository.communalPrefsRepository
-import com.android.systemui.communal.data.repository.communalRepository
import com.android.systemui.communal.data.repository.communalWidgetRepository
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.flags.Flags
@@ -45,7 +44,7 @@
applicationScope = applicationCoroutineScope,
bgDispatcher = testDispatcher,
broadcastDispatcher = broadcastDispatcher,
- communalRepository = communalRepository,
+ communalSceneInteractor = communalSceneInteractor,
widgetRepository = communalWidgetRepository,
communalPrefsRepository = communalPrefsRepository,
mediaRepository = communalMediaRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt
new file mode 100644
index 0000000..ee48c10
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.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.communal.domain.interactor
+
+import com.android.systemui.communal.data.repository.communalSceneRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+
+val Kosmos.communalSceneInteractor: CommunalSceneInteractor by
+ Kosmos.Fixture {
+ CommunalSceneInteractor(
+ applicationScope = applicationCoroutineScope,
+ communalSceneRepository = communalSceneRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
index 93e0b41..d558c96 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
@@ -40,6 +40,9 @@
private val _isRunning = MutableStateFlow(false)
override val isRunning: Flow<Boolean>
get() = _isRunning
+
+ override val isEngaged: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
fun setIsRunning(value: Boolean) {
_isRunning.value = value
}
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 96a4049..1a45c42 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
@@ -125,6 +125,9 @@
private val _isEncryptedOrLockdown = MutableStateFlow(true)
override val isEncryptedOrLockdown: Flow<Boolean> = _isEncryptedOrLockdown
+ private val _isKeyguardEnabled = MutableStateFlow(true)
+ override val isKeyguardEnabled: StateFlow<Boolean> = _isKeyguardEnabled.asStateFlow()
+
override val topClippingBounds = MutableStateFlow<Int?>(null)
override fun setQuickSettingsVisible(isVisible: Boolean) {
@@ -184,6 +187,10 @@
_clockShouldBeCentered.value = shouldBeCentered
}
+ override fun setKeyguardEnabled(enabled: Boolean) {
+ _isKeyguardEnabled.value = enabled
+ }
+
fun dozeTimeTick(millis: Long) {
_dozeTimeTick.value = millis
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
index bbe37c1..42af25e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -34,5 +35,6 @@
keyguardInteractor = keyguardInteractor,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ deviceEntryRepository = deviceEntryRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
index 23dcd96..edf77a0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -36,5 +37,6 @@
communalInteractor = communalInteractor,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ deviceEntryRepository = deviceEntryRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
index 604d9e4..4039ee6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
@@ -19,6 +19,7 @@
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
@@ -38,5 +39,7 @@
communalInteractor = communalInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
biometricSettingsRepository = biometricSettingsRepository,
+ keyguardRepository = keyguardRepository,
+ keyguardEnabledInteractor = keyguardEnabledInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
index 7eef704..be8048e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
@@ -36,6 +36,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -89,6 +90,7 @@
{ mock(DeviceEntryFingerprintAuthInteractor::class.java) },
{ mock(KeyguardInteractor::class.java) },
{ mock(KeyguardTransitionInteractor::class.java) },
+ { mock(SceneInteractor::class.java) },
testScope.backgroundScope,
)
val powerInteractorWithDeps =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt
new file mode 100644
index 0000000..0667a6b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+
+val Kosmos.keyguardEnabledInteractor by
+ Kosmos.Fixture {
+ KeyguardEnabledInteractor(
+ applicationCoroutineScope,
+ keyguardRepository,
+ biometricSettingsRepository,
+ keyguardTransitionInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt
index d344b75..5b1f95a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt
@@ -20,8 +20,8 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.mediaprojection.data.repository.MediaProjectionManagerRepository
import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconLabelVisibilityRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconLabelVisibilityRepositoryKosmos.kt
new file mode 100644
index 0000000..277dbb7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconLabelVisibilityRepositoryKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.qs.panels.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.iconLabelVisibilityRepository by Kosmos.Fixture { IconLabelVisibilityRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorKosmos.kt
new file mode 100644
index 0000000..7b9e4a1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorKosmos.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.qs.panels.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.qs.panels.data.repository.iconLabelVisibilityRepository
+
+val Kosmos.iconLabelVisibilityInteractor by
+ Kosmos.Fixture {
+ IconLabelVisibilityInteractor(
+ iconLabelVisibilityRepository,
+ FakeLogBuffer.Factory.create(),
+ applicationCoroutineScope
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
index 34b266a..82cfaf5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
@@ -18,6 +18,8 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
+import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.infiniteGridSizeViewModel
val Kosmos.infiniteGridLayout by
- Kosmos.Fixture { InfiniteGridLayout(iconTilesInteractor, infiniteGridSizeInteractor) }
+ Kosmos.Fixture { InfiniteGridLayout(iconTilesViewModel, infiniteGridSizeViewModel) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
index 4febfe91..37c9552 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
@@ -18,6 +18,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout
+import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel
val Kosmos.partitionedGridLayout by
- Kosmos.Fixture { PartitionedGridLayout(iconTilesInteractor, infiniteGridSizeInteractor) }
+ Kosmos.Fixture { PartitionedGridLayout(partitionedGridViewModel) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModelKosmos.kt
new file mode 100644
index 0000000..daf6087
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModelKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.domain.interactor.iconLabelVisibilityInteractor
+
+val Kosmos.iconLabelVisibilityViewModel by
+ Kosmos.Fixture { IconLabelVisibilityViewModelImpl(iconLabelVisibilityInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModelKosmos.kt
new file mode 100644
index 0000000..89b42a6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModelKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.qs.panels.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.domain.interactor.iconTilesInteractor
+
+val Kosmos.iconTilesViewModel by Kosmos.Fixture { IconTilesViewModelImpl(iconTilesInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridSizeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridSizeViewModelKosmos.kt
new file mode 100644
index 0000000..f6dfb8b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridSizeViewModelKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.domain.interactor.infiniteGridSizeInteractor
+
+val Kosmos.infiniteGridSizeViewModel by
+ Kosmos.Fixture { InfiniteGridSizeViewModelImpl(infiniteGridSizeInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModelKosmos.kt
new file mode 100644
index 0000000..b07cc7d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModelKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.qs.panels.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.partitionedGridViewModel by
+ Kosmos.Fixture {
+ PartitionedGridViewModel(
+ iconTilesViewModel,
+ infiniteGridSizeViewModel,
+ iconLabelVisibilityViewModel,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
index 4f5c9b4..5b6fd8c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
@@ -45,6 +45,7 @@
other ?: return
}
check("icon").that(actual.icon()).isEqualTo(other.icon())
+ check("iconRes").that(actual.iconRes).isEqualTo(other.iconRes)
check("label").that(actual.label).isEqualTo(other.label)
check("activationState").that(actual.activationState).isEqualTo(other.activationState)
check("secondaryLabel").that(actual.secondaryLabel).isEqualTo(other.secondaryLabel)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
index e0f60e9..b6194e3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
@@ -19,8 +19,8 @@
import android.content.Context
import android.view.View
import com.android.systemui.settings.brightness.MirrorController
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
@@ -41,7 +41,7 @@
override val customizerAnimationDuration = _animationDuration.asStateFlow()
private val _view = MutableStateFlow<View?>(null)
- override val qsView: Flow<View> = _view.filterNotNull()
+ override val qsView: StateFlow<View?> = _view.asStateFlow()
private val _state = MutableStateFlow<QSSceneAdapter.State?>(null)
val state = _state.filterNotNull()
@@ -64,6 +64,8 @@
}
}
+ override fun applyLatestExpansionAndSquishiness() {}
+
fun setCustomizing(value: Boolean) {
updateCustomizerFlows(if (value) CustomizerState.Showing else CustomizerState.Hidden)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/FakeOngoingActivityChipInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/FakeOngoingActivityChipInteractor.kt
index cd08274..90d459b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/FakeOngoingActivityChipInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/FakeOngoingActivityChipInteractor.kt
@@ -17,15 +17,9 @@
package com.android.systemui.statusbar.chips.ui.viewmodel
import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInteractor
-import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor
-import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.domain.model.OngoingActivityChipModel
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeScreenRecordChipInteractor : ScreenRecordChipInteractor() {
- override val chip: MutableStateFlow<OngoingActivityChipModel> =
- MutableStateFlow(OngoingActivityChipModel.Hidden)
-}
-
class FakeCallChipInteractor : CallChipInteractor() {
override val chip: MutableStateFlow<OngoingActivityChipModel> =
MutableStateFlow(OngoingActivityChipModel.Hidden)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt
index ffbaa7f..9e02df9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt
@@ -17,10 +17,20 @@
package com.android.systemui.statusbar.chips.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
+import com.android.systemui.screenrecord.data.repository.screenRecordRepository
+import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor
+import com.android.systemui.util.time.fakeSystemClock
-val Kosmos.screenRecordChipInteractor: FakeScreenRecordChipInteractor by
- Kosmos.Fixture { FakeScreenRecordChipInteractor() }
+val Kosmos.screenRecordChipInteractor: ScreenRecordChipInteractor by
+ Kosmos.Fixture {
+ ScreenRecordChipInteractor(
+ scope = applicationCoroutineScope,
+ screenRecordRepository = screenRecordRepository,
+ systemClock = fakeSystemClock,
+ )
+ }
val Kosmos.callChipInteractor: FakeCallChipInteractor by Kosmos.Fixture { FakeCallChipInteractor() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt
index 0e909c4..f19ac1e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.policy
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
+import org.mockito.Mockito.mock
-var Kosmos.keyguardStateController by Kosmos.Fixture { mock<KeyguardStateController>() }
+var Kosmos.keyguardStateController: KeyguardStateController by
+ Kosmos.Fixture { mock(KeyguardStateController::class.java) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepositoryKosmos.kt
new file mode 100644
index 0000000..2ba1211
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepositoryKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.data.repository
+
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.volumePanelGlobalStateRepository by
+ Kosmos.Fixture { VolumePanelGlobalStateRepository(dumpManager) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/VolumePanelGlobalStateInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/VolumePanelGlobalStateInteractorKosmos.kt
new file mode 100644
index 0000000..6e52364
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/VolumePanelGlobalStateInteractorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.panel.data.repository.volumePanelGlobalStateRepository
+
+val Kosmos.volumePanelGlobalStateInteractor: VolumePanelGlobalStateInteractor by
+ Kosmos.Fixture { VolumePanelGlobalStateInteractor(volumePanelGlobalStateRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt
index a606588..34a008f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt
@@ -23,6 +23,7 @@
import com.android.systemui.statusbar.policy.configurationController
import com.android.systemui.volume.panel.dagger.factory.volumePanelComponentFactory
import com.android.systemui.volume.panel.domain.VolumePanelStartable
+import com.android.systemui.volume.panel.domain.interactor.volumePanelGlobalStateInteractor
var Kosmos.volumePanelStartables: Set<VolumePanelStartable> by Kosmos.Fixture { emptySet() }
@@ -36,5 +37,6 @@
volumePanelComponentFactory,
configurationController,
broadcastDispatcher,
+ volumePanelGlobalStateInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorKosmos.kt
index 63b3f23..c3300f5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorKosmos.kt
@@ -24,6 +24,7 @@
import com.android.systemui.statusbar.phone.systemUIDialogFactory
import com.android.systemui.util.mockito.mock
import com.android.systemui.volume.VolumePanelFactory
+import com.android.systemui.volume.panel.domain.interactor.volumePanelGlobalStateInteractor
import com.android.systemui.volume.panel.ui.viewmodel.volumePanelViewModelFactory
val Kosmos.volumeNavigator by
@@ -36,5 +37,6 @@
volumePanelViewModelFactory,
systemUIDialogFactory,
uiEventLoggerFake,
+ volumePanelGlobalStateInteractor,
)
}
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 95cbb6b..48bc803 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -56,11 +56,52 @@
visibility: ["//visibility:public"],
}
+// This and the next module contain the same classes with different implementations.
+// "ravenwood-runtime-common-device" will be statically linked in device side tests.
+// "ravenwood-runtime-common-ravenwood" will only exist in ravenwood-runtime, which will take
+// precedence even if the test jar (accidentally) contains "ravenwood-runtime-common-device".
+// "ravenwood-runtime-common" uses it to detect if the rutime is Ravenwood or not.
+java_library {
+ name: "ravenwood-runtime-common-ravenwood",
+ host_supported: true,
+ sdk_version: "core_current",
+ srcs: [
+ "runtime-common-ravenwood-src/**/*.java",
+ ],
+ visibility: ["//frameworks/base"],
+}
+
+java_library {
+ name: "ravenwood-runtime-common-device",
+ host_supported: true,
+ sdk_version: "core_current",
+ srcs: [
+ "runtime-common-device-src/**/*.java",
+ ],
+ visibility: ["//visibility:private"],
+}
+
+java_library {
+ name: "ravenwood-runtime-common",
+ host_supported: true,
+ sdk_version: "core_current",
+ srcs: [
+ "runtime-common-src/**/*.java",
+ ],
+ libs: [
+ "ravenwood-runtime-common-ravenwood",
+ ],
+ visibility: ["//visibility:private"],
+}
+
java_library_host {
name: "ravenwood-helper-libcore-runtime.host",
srcs: [
"runtime-helper-src/libcore-fake/**/*.java",
],
+ static_libs: [
+ "ravenwood-runtime-common",
+ ],
visibility: ["//visibility:private"],
}
@@ -77,6 +118,9 @@
srcs: [
"runtime-helper-src/framework/**/*.java",
],
+ static_libs: [
+ "ravenwood-runtime-common",
+ ],
libs: [
"framework-minus-apex.ravenwood",
"ravenwood-junit",
@@ -105,6 +149,7 @@
],
static_libs: [
"androidx.test.monitor-for-device",
+ "ravenwood-runtime-common",
],
libs: [
"android.test.mock",
@@ -145,6 +190,10 @@
"junit-flag-src/**/*.java",
],
sdk_version: "test_current",
+ static_libs: [
+ "ravenwood-runtime-common",
+ "ravenwood-runtime-common-device",
+ ],
libs: [
"junit",
"flag-junit",
@@ -199,7 +248,7 @@
],
srcs: [
- "runtime-helper-src/jni/*.cpp",
+ "runtime-jni/*.cpp",
],
shared_libs: [
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 5506a46..49e793f 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -86,10 +86,6 @@
sPendingUncaughtException.compareAndSet(null, throwable);
};
- public static boolean isOnRavenwood() {
- return true;
- }
-
public static void init(RavenwoodRule rule) {
if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
maybeThrowPendingUncaughtException(false);
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 9d12f85..68b5aeb 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -29,6 +29,8 @@
import android.platform.test.annotations.EnabledOnRavenwood;
import android.platform.test.annotations.IgnoreUnderRavenwood;
+import com.android.ravenwood.common.RavenwoodCommonUtils;
+
import org.junit.Assume;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
@@ -54,7 +56,7 @@
* before a test class is fully initialized.
*/
public class RavenwoodRule implements TestRule {
- static final boolean IS_ON_RAVENWOOD = RavenwoodRuleImpl.isOnRavenwood();
+ static final boolean IS_ON_RAVENWOOD = RavenwoodCommonUtils.isOnRavenwood();
/**
* When probing is enabled, all tests will be unconditionally run on Ravenwood to detect
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
index c3786ee..5f1b0c2 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -16,6 +16,8 @@
package android.platform.test.ravenwood;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_SYSPROP;
+
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -101,6 +103,8 @@
setValue("ro.soc.model", "Ravenwood");
setValue("ro.debuggable", "1");
+
+ setValue(RAVENWOOD_SYSPROP, "1");
}
/** Copy constructor */
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java
index 99ab327..19c1bff 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java
@@ -15,9 +15,7 @@
*/
package android.platform.test.ravenwood;
-import java.io.File;
-import java.io.PrintStream;
-import java.util.Arrays;
+import com.android.ravenwood.common.RavenwoodCommonUtils;
/**
* Utilities for writing (bivalent) ravenwood tests.
@@ -26,15 +24,6 @@
private RavenwoodUtils() {
}
- private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime";
-
- // LibcoreRavenwoodUtils calls it with reflections.
- public static void loadRavenwoodNativeRuntime() {
- if (RavenwoodRule.isOnRavenwood()) {
- RavenwoodUtils.loadJniLibrary(RAVENWOOD_NATIVE_RUNTIME_NAME);
- }
- }
-
/**
* Load a JNI library respecting {@code java.library.path}
* (which reflects {@code LD_LIBRARY_PATH}).
@@ -56,85 +45,6 @@
* it uses {@code JNI_OnLoad()} as the entry point name on both.
*/
public static void loadJniLibrary(String libname) {
- if (RavenwoodRule.isOnRavenwood()) {
- loadLibraryOnRavenwood(libname);
- } else {
- // Just delegate to the loadLibrary().
- System.loadLibrary(libname);
- }
- }
-
- private static void loadLibraryOnRavenwood(String libname) {
- var path = System.getProperty("java.library.path");
- var filename = "lib" + libname + ".so";
-
- System.out.println("Looking for library " + libname + ".so in java.library.path:" + path);
-
- try {
- if (path == null) {
- throw new UnsatisfiedLinkError("Cannot load library " + libname + "."
- + " Property java.library.path not set!");
- }
- for (var dir : path.split(":")) {
- var file = new File(dir + "/" + filename);
- if (file.exists()) {
- System.load(file.getAbsolutePath());
- return;
- }
- }
- throw new UnsatisfiedLinkError("Library " + libname + " not found in "
- + "java.library.path: " + path);
- } catch (Throwable e) {
- dumpFiles(System.out);
- throw e;
- }
- }
-
- private static void dumpFiles(PrintStream out) {
- try {
- var path = System.getProperty("java.library.path");
- out.println("# java.library.path=" + path);
-
- for (var dir : path.split(":")) {
- listFiles(out, new File(dir), "");
-
- var gparent = new File((new File(dir)).getAbsolutePath() + "../../..")
- .getCanonicalFile();
- if (gparent.getName().contains("testcases")) {
- // Special case: if we found this directory, dump its contents too.
- listFiles(out, gparent, "");
- }
- }
-
- var gparent = new File("../..").getCanonicalFile();
- out.println("# ../..=" + gparent);
- listFiles(out, gparent, "");
- } catch (Throwable th) {
- out.println("Error: " + th.toString());
- th.printStackTrace(out);
- }
- }
-
- private static void listFiles(PrintStream out, File dir, String prefix) {
- if (!dir.isDirectory()) {
- out.println(prefix + dir.getAbsolutePath() + " is not a directory!");
- return;
- }
- out.println(prefix + ":" + dir.getAbsolutePath() + "/");
- // First, list the files.
- for (var file : Arrays.stream(dir.listFiles()).sorted().toList()) {
- out.println(prefix + " " + file.getName() + "" + (file.isDirectory() ? "/" : ""));
- }
-
- // Then recurse.
- if (dir.getAbsolutePath().startsWith("/usr") || dir.getAbsolutePath().startsWith("/lib")) {
- // There would be too many files, so don't recurse.
- return;
- }
- for (var file : Arrays.stream(dir.listFiles()).sorted().toList()) {
- if (file.isDirectory()) {
- listFiles(out, file, prefix + " ");
- }
- }
+ RavenwoodCommonUtils.loadJniLibrary(libname);
}
}
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 773a89a..483b98a 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -20,10 +20,6 @@
import org.junit.runners.model.Statement;
public class RavenwoodRuleImpl {
- public static boolean isOnRavenwood() {
- return false;
- }
-
public static void init(RavenwoodRule rule) {
// No-op when running on a real device
}
diff --git a/ravenwood/runtime-common-device-src/com/android/ravenwood/common/divergence/RavenwoodDivergence.java b/ravenwood/runtime-common-device-src/com/android/ravenwood/common/divergence/RavenwoodDivergence.java
new file mode 100644
index 0000000..1714716
--- /dev/null
+++ b/ravenwood/runtime-common-device-src/com/android/ravenwood/common/divergence/RavenwoodDivergence.java
@@ -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.ravenwood.common.divergence;
+
+/**
+ * A class that behaves differently on the device side and on Ravenwood, because we have
+ * two build modules with different implementation and we link the different ones at runtime.
+ */
+public final class RavenwoodDivergence {
+ private RavenwoodDivergence() {
+ }
+
+ public static boolean isOnRavenwood() {
+ return false;
+ }
+}
diff --git a/ravenwood/runtime-common-ravenwood-src/com/android/ravenwood/common/divergence/RavenwoodDivergence.java b/ravenwood/runtime-common-ravenwood-src/com/android/ravenwood/common/divergence/RavenwoodDivergence.java
new file mode 100644
index 0000000..59f474a
--- /dev/null
+++ b/ravenwood/runtime-common-ravenwood-src/com/android/ravenwood/common/divergence/RavenwoodDivergence.java
@@ -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.ravenwood.common.divergence;
+
+/**
+ * A class that behaves differently on the device side and on Ravenwood, because we have
+ * two build modules with different implementation and we link the different ones at runtime.
+ */
+public final class RavenwoodDivergence {
+ private RavenwoodDivergence() {
+ }
+
+ public static boolean isOnRavenwood() {
+ return true;
+ }
+}
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/JvmWorkaround.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/JvmWorkaround.java
new file mode 100644
index 0000000..ee28099
--- /dev/null
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/JvmWorkaround.java
@@ -0,0 +1,68 @@
+/*
+ * 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.ravenwood.common;
+
+import java.io.FileDescriptor;
+
+/**
+ * Collection of methods to workaround limitation in the hostside JVM.
+ */
+public abstract class JvmWorkaround {
+ JvmWorkaround() {
+ }
+
+ // We only support OpenJDK for now.
+ private static JvmWorkaround sInstance =
+ RavenwoodCommonUtils.isOnRavenwood() ? new OpenJdkWorkaround() : new NullWorkaround();
+
+ public static JvmWorkaround getInstance() {
+ return sInstance;
+ }
+
+ /**
+ * Equivalent to Android's FileDescriptor.setInt$().
+ */
+ public abstract void setFdInt(FileDescriptor fd, int fdInt);
+
+
+ /**
+ * Equivalent to Android's FileDescriptor.getInt$().
+ */
+ public abstract int getFdInt(FileDescriptor fd);
+
+ /**
+ * Placeholder implementation for the host side.
+ *
+ * Even on the host side, we don't want to throw just because the class is loaded,
+ * which could cause weird random issues, so we throw from individual methods rather
+ * than from the constructor.
+ */
+ private static class NullWorkaround extends JvmWorkaround {
+ private RuntimeException calledOnHostside() {
+ throw new RuntimeException("This method shouldn't be called on the host side");
+ }
+
+ @Override
+ public void setFdInt(FileDescriptor fd, int fdInt) {
+ throw calledOnHostside();
+ }
+
+ @Override
+ public int getFdInt(FileDescriptor fd) {
+ throw calledOnHostside();
+ }
+ }
+}
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/OpenJdkWorkaround.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/OpenJdkWorkaround.java
new file mode 100644
index 0000000..9aedaab
--- /dev/null
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/OpenJdkWorkaround.java
@@ -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.ravenwood.common;
+
+import java.io.FileDescriptor;
+
+class OpenJdkWorkaround extends JvmWorkaround {
+ @Override
+ public void setFdInt(FileDescriptor fd, int fdInt) {
+ try {
+ final Object obj = Class.forName("jdk.internal.access.SharedSecrets").getMethod(
+ "getJavaIOFileDescriptorAccess").invoke(null);
+ Class.forName("jdk.internal.access.JavaIOFileDescriptorAccess").getMethod(
+ "set", FileDescriptor.class, int.class).invoke(obj, fd, fdInt);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException("Failed to interact with raw FileDescriptor internals;"
+ + " perhaps JRE has changed?", e);
+ }
+ }
+
+ @Override
+ public int getFdInt(FileDescriptor fd) {
+ try {
+ final Object obj = Class.forName("jdk.internal.access.SharedSecrets").getMethod(
+ "getJavaIOFileDescriptorAccess").invoke(null);
+ return (int) Class.forName("jdk.internal.access.JavaIOFileDescriptorAccess").getMethod(
+ "get", FileDescriptor.class).invoke(obj, fd);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException("Failed to interact with raw FileDescriptor internals;"
+ + " perhaps JRE has changed?", e);
+ }
+ }
+}
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodBadIntegrityException.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodBadIntegrityException.java
new file mode 100644
index 0000000..61d54cb
--- /dev/null
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodBadIntegrityException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwood.common;
+
+public class RavenwoodBadIntegrityException extends RavenwoodRuntimeException {
+ public RavenwoodBadIntegrityException(String message) {
+ super(message);
+ }
+
+ public RavenwoodBadIntegrityException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
new file mode 100644
index 0000000..c8cc8d9
--- /dev/null
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
@@ -0,0 +1,234 @@
+/*
+ * 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.ravenwood.common;
+
+import com.android.ravenwood.common.divergence.RavenwoodDivergence;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.PrintStream;
+import java.util.Arrays;
+
+public class RavenwoodCommonUtils {
+ private static final String TAG = "RavenwoodCommonUtils";
+
+ private RavenwoodCommonUtils() {
+ }
+
+ private static final Object sLock = new Object();
+
+ /** Name of `libravenwood_runtime` */
+ private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime";
+
+ /** Directory name of `out/host/linux-x86/testcases/ravenwood-runtime` */
+ private static final String RAVENWOOD_RUNTIME_DIR_NAME = "ravenwood-runtime";
+
+ private static boolean sEnableExtraRuntimeCheck =
+ "1".equals(System.getenv("RAVENWOOD_ENABLE_EXTRA_RUNTIME_CHECK"));
+
+ private static final boolean IS_ON_RAVENWOOD = RavenwoodDivergence.isOnRavenwood();
+
+ private static final String RAVEWOOD_RUNTIME_PATH = getRavenwoodRuntimePathInternal();
+
+ public static final String RAVENWOOD_SYSPROP = "ro.is_on_ravenwood";
+
+ // @GuardedBy("sLock")
+ private static boolean sIntegrityChecked = false;
+
+ /**
+ * @return if we're running on Ravenwood.
+ */
+ public static boolean isOnRavenwood() {
+ return IS_ON_RAVENWOOD;
+ }
+
+ /**
+ * Throws if the runtime is not Ravenwood.
+ */
+ public static void ensureOnRavenwood() {
+ if (!isOnRavenwood()) {
+ throw new RavenwoodRuntimeException("This is only supposed to be used on Ravenwood");
+ }
+ }
+
+ /**
+ * @return if the various extra runtime check should be enabled.
+ */
+ public static boolean shouldEnableExtraRuntimeCheck() {
+ return sEnableExtraRuntimeCheck;
+ }
+
+ /**
+ * Load the main runtime JNI library.
+ */
+ public static void loadRavenwoodNativeRuntime() {
+ ensureOnRavenwood();
+ loadJniLibrary(RAVENWOOD_NATIVE_RUNTIME_NAME);
+ }
+
+ /**
+ * Internal implementation of
+ * {@link android.platform.test.ravenwood.RavenwoodUtils#loadJniLibrary(String)}
+ */
+ public static void loadJniLibrary(String libname) {
+ if (RavenwoodCommonUtils.isOnRavenwood()) {
+ loadJniLibraryInternal(libname);
+ } else {
+ System.loadLibrary(libname);
+ }
+ }
+
+ /**
+ * Function equivalent to ART's System.loadLibrary. See RavenwoodUtils for why we need it.
+ */
+ private static void loadJniLibraryInternal(String libname) {
+ var path = System.getProperty("java.library.path");
+ var filename = "lib" + libname + ".so";
+
+ System.out.println("Looking for library " + libname + ".so in java.library.path:" + path);
+
+ try {
+ if (path == null) {
+ throw new UnsatisfiedLinkError("Cannot load library " + libname + "."
+ + " Property java.library.path not set!");
+ }
+ for (var dir : path.split(":")) {
+ var file = new File(dir + "/" + filename);
+ if (file.exists()) {
+ System.load(file.getAbsolutePath());
+ return;
+ }
+ }
+ throw new UnsatisfiedLinkError("Library " + libname + " not found in "
+ + "java.library.path: " + path);
+ } catch (Throwable e) {
+ dumpFiles(System.out);
+ throw e;
+ }
+ }
+
+ private static void dumpFiles(PrintStream out) {
+ try {
+ var path = System.getProperty("java.library.path");
+ out.println("# java.library.path=" + path);
+
+ for (var dir : path.split(":")) {
+ listFiles(out, new File(dir), "");
+
+ var gparent = new File((new File(dir)).getAbsolutePath() + "../../..")
+ .getCanonicalFile();
+ if (gparent.getName().contains("testcases")) {
+ // Special case: if we found this directory, dump its contents too.
+ listFiles(out, gparent, "");
+ }
+ }
+
+ var gparent = new File("../..").getCanonicalFile();
+ out.println("# ../..=" + gparent);
+ listFiles(out, gparent, "");
+ } catch (Throwable th) {
+ out.println("Error: " + th.toString());
+ th.printStackTrace(out);
+ }
+ }
+
+ private static void listFiles(PrintStream out, File dir, String prefix) {
+ if (!dir.isDirectory()) {
+ out.println(prefix + dir.getAbsolutePath() + " is not a directory!");
+ return;
+ }
+ out.println(prefix + ":" + dir.getAbsolutePath() + "/");
+ // First, list the files.
+ for (var file : Arrays.stream(dir.listFiles()).sorted().toList()) {
+ out.println(prefix + " " + file.getName() + "" + (file.isDirectory() ? "/" : ""));
+ }
+
+ // Then recurse.
+ if (dir.getAbsolutePath().startsWith("/usr") || dir.getAbsolutePath().startsWith("/lib")) {
+ // There would be too many files, so don't recurse.
+ return;
+ }
+ for (var file : Arrays.stream(dir.listFiles()).sorted().toList()) {
+ if (file.isDirectory()) {
+ listFiles(out, file, prefix + " ");
+ }
+ }
+ }
+
+ /**
+ * @return the full directory path that contains the "ravenwood-runtime" files.
+ *
+ * This method throws if called on the device side.
+ */
+ public static String getRavenwoodRuntimePath() {
+ ensureOnRavenwood();
+ return RAVEWOOD_RUNTIME_PATH;
+ }
+
+ private static String getRavenwoodRuntimePathInternal() {
+ if (!isOnRavenwood()) {
+ return null;
+ }
+ var path = System.getProperty("java.library.path");
+
+ System.out.println("Looking for " + RAVENWOOD_RUNTIME_DIR_NAME + " directory"
+ + " in java.library.path:" + path);
+
+ try {
+ if (path == null) {
+ throw new IllegalStateException("java.library.path shouldn't be null");
+ }
+ for (var dir : path.split(":")) {
+
+ // For each path, see if the path contains RAVENWOOD_RUNTIME_DIR_NAME.
+ var d = new File(dir);
+ for (;;) {
+ if (d.getParent() == null) {
+ break; // Root dir, stop.
+ }
+ if (RAVENWOOD_RUNTIME_DIR_NAME.equals(d.getName())) {
+ var ret = d.getAbsolutePath() + "/";
+ System.out.println("Found: " + ret);
+ return ret;
+ }
+ d = d.getParentFile();
+ }
+ }
+ throw new IllegalStateException(RAVENWOOD_RUNTIME_DIR_NAME + " not found");
+ } catch (Throwable e) {
+ dumpFiles(System.out);
+ throw e;
+ }
+ }
+
+ /** Close an {@link AutoCloseable}. */
+ public static void closeQuietly(AutoCloseable c) {
+ if (c != null) {
+ try {
+ c.close();
+ } catch (Exception e) {
+ // Ignore
+ }
+ }
+ }
+
+ /** Close a {@link FileDescriptor}. */
+ public static void closeQuietly(FileDescriptor fd) {
+ var is = new FileInputStream(fd);
+ RavenwoodCommonUtils.closeQuietly(is);
+ }
+}
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodRuntimeException.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodRuntimeException.java
new file mode 100644
index 0000000..7b0cebc
--- /dev/null
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodRuntimeException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwood.common;
+
+public class RavenwoodRuntimeException extends RuntimeException {
+ public RavenwoodRuntimeException(String message) {
+ super(message);
+ }
+
+ public RavenwoodRuntimeException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodRuntimeNative.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodRuntimeNative.java
new file mode 100644
index 0000000..6540221
--- /dev/null
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodRuntimeNative.java
@@ -0,0 +1,71 @@
+/*
+ * 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.ravenwood.common;
+
+import java.io.FileDescriptor;
+
+/**
+ * Class to host all the JNI methods used in ravenwood runtime.
+ */
+public class RavenwoodRuntimeNative {
+ private RavenwoodRuntimeNative() {
+ }
+
+ static {
+ RavenwoodCommonUtils.ensureOnRavenwood();
+ RavenwoodCommonUtils.loadRavenwoodNativeRuntime();
+ }
+
+ public static native void applyFreeFunction(long freeFunction, long nativePtr);
+
+ public static native long nLseek(int fd, long offset, int whence);
+
+ public static native int[] nPipe2(int flags);
+
+ public static native int nDup(int oldfd);
+
+ public static native int nFcntlInt(int fd, int cmd, int arg);
+
+ public static long lseek(FileDescriptor fd, long offset, int whence) {
+ return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence);
+ }
+
+ public static FileDescriptor[] pipe2(int flags) {
+ var fds = nPipe2(flags);
+ var ret = new FileDescriptor[] {
+ new FileDescriptor(),
+ new FileDescriptor(),
+ };
+ JvmWorkaround.getInstance().setFdInt(ret[0], fds[0]);
+ JvmWorkaround.getInstance().setFdInt(ret[1], fds[1]);
+
+ return ret;
+ }
+
+ public static FileDescriptor dup(FileDescriptor fd) {
+ var fdInt = nDup(JvmWorkaround.getInstance().getFdInt(fd));
+
+ var retFd = new java.io.FileDescriptor();
+ JvmWorkaround.getInstance().setFdInt(retFd, fdInt);
+ return retFd;
+ }
+
+ public static int fcntlInt(FileDescriptor fd, int cmd, int arg) {
+ var fdInt = JvmWorkaround.getInstance().getFdInt(fd);
+
+ return nFcntlInt(fdInt, cmd, arg);
+ }
+}
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java
index 2d79914..8fe6853 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java
@@ -26,6 +26,7 @@
import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;
import com.android.internal.annotations.GuardedBy;
+import com.android.ravenwood.common.JvmWorkaround;
import java.io.File;
import java.io.FileDescriptor;
@@ -46,27 +47,11 @@
private static final Map<FileDescriptor, RandomAccessFile> sActive = new HashMap<>();
public static void native_setFdInt$ravenwood(FileDescriptor fd, int fdInt) {
- try {
- final Object obj = Class.forName("jdk.internal.access.SharedSecrets").getMethod(
- "getJavaIOFileDescriptorAccess").invoke(null);
- Class.forName("jdk.internal.access.JavaIOFileDescriptorAccess").getMethod(
- "set", FileDescriptor.class, int.class).invoke(obj, fd, fdInt);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("Failed to interact with raw FileDescriptor internals;"
- + " perhaps JRE has changed?", e);
- }
+ JvmWorkaround.getInstance().setFdInt(fd, fdInt);
}
public static int native_getFdInt$ravenwood(FileDescriptor fd) {
- try {
- final Object obj = Class.forName("jdk.internal.access.SharedSecrets").getMethod(
- "getJavaIOFileDescriptorAccess").invoke(null);
- return (int) Class.forName("jdk.internal.access.JavaIOFileDescriptorAccess").getMethod(
- "get", FileDescriptor.class).invoke(obj, fd);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("Failed to interact with raw FileDescriptor internals;"
- + " perhaps JRE has changed?", e);
- }
+ return JvmWorkaround.getInstance().getFdInt(fd);
}
public static FileDescriptor native_open$ravenwood(File file, int pfdMode) throws IOException {
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java
index 68bf922..b00cee0 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java
@@ -19,6 +19,7 @@
import android.util.Log;
import com.android.internal.ravenwood.RavenwoodEnvironment;
+import com.android.ravenwood.common.RavenwoodCommonUtils;
public class RavenwoodEnvironment_host {
private static final String TAG = RavenwoodEnvironment.TAG;
@@ -39,7 +40,7 @@
if (sInitialized) {
return;
}
- Log.w(TAG, "Initializing Ravenwood environment");
+ Log.i(TAG, "Initializing Ravenwood environment");
// Set the default values.
var sysProps = RavenwoodSystemProperties.DEFAULT_VALUES;
@@ -54,4 +55,4 @@
sInitialized = true;
}
}
-}
+}
\ No newline at end of file
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
index 69ff262..e198646 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
@@ -15,7 +15,7 @@
*/
package com.android.platform.test.ravenwood.runtimehelper;
-import android.platform.test.ravenwood.RavenwoodUtils;
+import com.android.ravenwood.common.RavenwoodCommonUtils;
import java.io.File;
import java.lang.reflect.Modifier;
@@ -141,7 +141,7 @@
log("Loading " + LIBANDROID_RUNTIME_NAME + " for '" + libanrdoidClasses + "' and '"
+ libhwuiClasses + "'");
- RavenwoodUtils.loadJniLibrary(LIBANDROID_RUNTIME_NAME);
+ RavenwoodCommonUtils.loadJniLibrary(LIBANDROID_RUNTIME_NAME);
}
/**
diff --git a/ravenwood/runtime-helper-src/jni/ravenwood_runtime.cpp b/ravenwood/runtime-helper-src/jni/ravenwood_runtime.cpp
deleted file mode 100644
index 8e3a21d..0000000
--- a/ravenwood/runtime-helper-src/jni/ravenwood_runtime.cpp
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.
- */
-
-#include <nativehelper/JNIHelp.h>
-#include "jni.h"
-#include "utils/Log.h"
-#include "utils/misc.h"
-
-
-typedef void (*FreeFunction)(void*);
-
-static void NativeAllocationRegistry_applyFreeFunction(JNIEnv*,
- jclass,
- jlong freeFunction,
- jlong ptr) {
- void* nativePtr = reinterpret_cast<void*>(static_cast<uintptr_t>(ptr));
- FreeFunction nativeFreeFunction
- = reinterpret_cast<FreeFunction>(static_cast<uintptr_t>(freeFunction));
- nativeFreeFunction(nativePtr);
-}
-
-static const JNINativeMethod sMethods_NAR[] =
-{
- { "applyFreeFunction", "(JJ)V", (void*)NativeAllocationRegistry_applyFreeFunction },
-};
-
-extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
-{
- JNIEnv* env = NULL;
- jint result = -1;
-
- if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
- ALOGE("GetEnv failed!");
- return result;
- }
- ALOG_ASSERT(env, "Could not retrieve the env!");
-
- ALOGI("%s: JNI_OnLoad", __FILE__);
-
- // Initialize the Ravenwood version of NativeAllocationRegistry.
- // We don't use this JNI on the device side, but if we ever have to do, skip this part.
-#ifndef __ANDROID__
- int res = jniRegisterNativeMethods(env, "libcore/util/NativeAllocationRegistry",
- sMethods_NAR, NELEM(sMethods_NAR));
- if (res < 0) {
- return res;
- }
-#endif
-
- return JNI_VERSION_1_4;
-}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/ErrnoException.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/ErrnoException.java
index 388156a..843455d 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/android/system/ErrnoException.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/ErrnoException.java
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+// [ravenwood] Copied from libcore.
+
package android.system;
import java.io.IOException;
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
new file mode 100644
index 0000000..e031eb2
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
@@ -0,0 +1,45 @@
+/*
+ * 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 android.system;
+
+import com.android.ravenwood.common.RavenwoodRuntimeNative;
+
+import java.io.FileDescriptor;
+
+/**
+ * OS class replacement used on Ravenwood. For now, we just implement APIs as we need them...
+ * TODO(b/340887115): Need a better integration with libcore.
+ */
+public final class Os {
+ private Os() {}
+
+ public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException {
+ return RavenwoodRuntimeNative.lseek(fd, offset, whence);
+ }
+
+
+ public static FileDescriptor[] pipe2(int flags) throws ErrnoException {
+ return RavenwoodRuntimeNative.pipe2(flags);
+ }
+
+ public static FileDescriptor dup(FileDescriptor fd) throws ErrnoException {
+ return RavenwoodRuntimeNative.dup(fd);
+ }
+
+ public static int fcntlInt(FileDescriptor fd, int cmd, int arg) throws ErrnoException {
+ return RavenwoodRuntimeNative.fcntlInt(fd, cmd, arg);
+ }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/OsConstants.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/OsConstants.java
new file mode 100644
index 0000000..c56ec8a
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/OsConstants.java
@@ -0,0 +1,1259 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.system;
+
+import com.android.ravenwood.common.RavenwoodCommonUtils;
+
+/**
+ * Copied from libcore's version, with the local changes:
+ * - All the imports are removed. (they're only used in javadoc)
+ * - All the annotations are removed.
+ * - The initConstants() method is moved to a nested class.
+ *
+ * TODO(b/340887115): Need a better integration with libcore.
+ */
+
+public class OsConstants {
+// @UnsupportedAppUsage
+ private OsConstants() {
+ }
+
+ /**
+ * Returns the index of the element in the {@link StructCapUserData} (cap_user_data)
+ * array that this capability is stored in.
+ *
+ * @param x capability
+ * @return index of the element in the {@link StructCapUserData} array storing this capability
+ *
+ * @hide
+ */
+// @UnsupportedAppUsage
+// @SystemApi(client = MODULE_LIBRARIES)
+ public static int CAP_TO_INDEX(int x) { return x >>> 5; }
+
+ /**
+ * Returns the mask for the given capability. This is relative to the capability's
+ * {@link StructCapUserData} (cap_user_data) element, the index of which can be
+ * retrieved with {@link CAP_TO_INDEX}.
+ *
+ * @param x capability
+ * @return mask for given capability
+ *
+ * @hide
+ */
+// @UnsupportedAppUsage
+// @SystemApi(client = MODULE_LIBRARIES)
+ public static int CAP_TO_MASK(int x) { return 1 << (x & 31); }
+
+ /**
+ * Tests whether the given mode is a block device.
+ */
+ public static boolean S_ISBLK(int mode) { return (mode & S_IFMT) == S_IFBLK; }
+
+ /**
+ * Tests whether the given mode is a character device.
+ */
+ public static boolean S_ISCHR(int mode) { return (mode & S_IFMT) == S_IFCHR; }
+
+ /**
+ * Tests whether the given mode is a directory.
+ */
+ public static boolean S_ISDIR(int mode) { return (mode & S_IFMT) == S_IFDIR; }
+
+ /**
+ * Tests whether the given mode is a FIFO.
+ */
+ public static boolean S_ISFIFO(int mode) { return (mode & S_IFMT) == S_IFIFO; }
+
+ /**
+ * Tests whether the given mode is a regular file.
+ */
+ public static boolean S_ISREG(int mode) { return (mode & S_IFMT) == S_IFREG; }
+
+ /**
+ * Tests whether the given mode is a symbolic link.
+ */
+ public static boolean S_ISLNK(int mode) { return (mode & S_IFMT) == S_IFLNK; }
+
+ /**
+ * Tests whether the given mode is a socket.
+ */
+ public static boolean S_ISSOCK(int mode) { return (mode & S_IFMT) == S_IFSOCK; }
+
+ /**
+ * Extracts the exit status of a child. Only valid if WIFEXITED returns true.
+ */
+ public static int WEXITSTATUS(int status) { return (status & 0xff00) >> 8; }
+
+ /**
+ * Tests whether the child dumped core. Only valid if WIFSIGNALED returns true.
+ */
+ public static boolean WCOREDUMP(int status) { return (status & 0x80) != 0; }
+
+ /**
+ * Returns the signal that caused the child to exit. Only valid if WIFSIGNALED returns true.
+ */
+ public static int WTERMSIG(int status) { return status & 0x7f; }
+
+ /**
+ * Returns the signal that cause the child to stop. Only valid if WIFSTOPPED returns true.
+ */
+ public static int WSTOPSIG(int status) { return WEXITSTATUS(status); }
+
+ /**
+ * Tests whether the child exited normally.
+ */
+ public static boolean WIFEXITED(int status) { return (WTERMSIG(status) == 0); }
+
+ /**
+ * Tests whether the child was stopped (not terminated) by a signal.
+ */
+ public static boolean WIFSTOPPED(int status) { return (WTERMSIG(status) == 0x7f); }
+
+ /**
+ * Tests whether the child was terminated by a signal.
+ */
+ public static boolean WIFSIGNALED(int status) { return (WTERMSIG(status + 1) >= 2); }
+
+ public static final int AF_INET = placeholder();
+ public static final int AF_INET6 = placeholder();
+ public static final int AF_NETLINK = placeholder();
+ public static final int AF_PACKET = placeholder();
+ public static final int AF_UNIX = placeholder();
+
+ /**
+ * The virt-vsock address family, linux specific.
+ * It is used with {@code struct sockaddr_vm} from uapi/linux/vm_sockets.h.
+ *
+ * @see <a href="https://man7.org/linux/man-pages/man7/vsock.7.html">vsock(7)</a>
+ * @see VmSocketAddress
+ */
+ public static final int AF_VSOCK = placeholder();
+ public static final int AF_UNSPEC = placeholder();
+ public static final int AI_ADDRCONFIG = placeholder();
+ public static final int AI_ALL = placeholder();
+ public static final int AI_CANONNAME = placeholder();
+ public static final int AI_NUMERICHOST = placeholder();
+ public static final int AI_NUMERICSERV = placeholder();
+ public static final int AI_PASSIVE = placeholder();
+ public static final int AI_V4MAPPED = placeholder();
+ public static final int ARPHRD_ETHER = placeholder();
+
+ /**
+ * The virtio-vsock {@code svmPort} value to bind for any available port.
+ *
+ * @see <a href="https://man7.org/linux/man-pages/man7/vsock.7.html">vsock(7)</a>
+ * @see VmSocketAddress
+ */
+ public static final int VMADDR_PORT_ANY = placeholder();
+
+ /**
+ * The virtio-vsock {@code svmCid} value to listens for all CIDs.
+ *
+ * @see <a href="https://man7.org/linux/man-pages/man7/vsock.7.html">vsock(7)</a>
+ * @see VmSocketAddress
+ */
+ public static final int VMADDR_CID_ANY = placeholder();
+
+ /**
+ * The virtio-vsock {@code svmCid} value for host communication.
+ *
+ * @see <a href="https://man7.org/linux/man-pages/man7/vsock.7.html">vsock(7)</a>
+ * @see VmSocketAddress
+ */
+ public static final int VMADDR_CID_LOCAL = placeholder();
+
+ /**
+ * The virtio-vsock {@code svmCid} value for loopback communication.
+ *
+ * @see <a href="https://man7.org/linux/man-pages/man7/vsock.7.html">vsock(7)</a>
+ * @see VmSocketAddress
+ */
+ public static final int VMADDR_CID_HOST = placeholder();
+
+ /**
+ * ARP protocol loopback device identifier.
+ *
+ * @hide
+ */
+// @UnsupportedAppUsage
+// @SystemApi(client = MODULE_LIBRARIES)
+ public static final int ARPHRD_LOOPBACK = placeholder();
+ public static final int CAP_AUDIT_CONTROL = placeholder();
+ public static final int CAP_AUDIT_WRITE = placeholder();
+ public static final int CAP_BLOCK_SUSPEND = placeholder();
+ public static final int CAP_CHOWN = placeholder();
+ public static final int CAP_DAC_OVERRIDE = placeholder();
+ public static final int CAP_DAC_READ_SEARCH = placeholder();
+ public static final int CAP_FOWNER = placeholder();
+ public static final int CAP_FSETID = placeholder();
+ public static final int CAP_IPC_LOCK = placeholder();
+ public static final int CAP_IPC_OWNER = placeholder();
+ public static final int CAP_KILL = placeholder();
+ public static final int CAP_LAST_CAP = placeholder();
+ public static final int CAP_LEASE = placeholder();
+ public static final int CAP_LINUX_IMMUTABLE = placeholder();
+ public static final int CAP_MAC_ADMIN = placeholder();
+ public static final int CAP_MAC_OVERRIDE = placeholder();
+ public static final int CAP_MKNOD = placeholder();
+ public static final int CAP_NET_ADMIN = placeholder();
+ public static final int CAP_NET_BIND_SERVICE = placeholder();
+ public static final int CAP_NET_BROADCAST = placeholder();
+ public static final int CAP_NET_RAW = placeholder();
+ public static final int CAP_SETFCAP = placeholder();
+ public static final int CAP_SETGID = placeholder();
+ public static final int CAP_SETPCAP = placeholder();
+ public static final int CAP_SETUID = placeholder();
+ public static final int CAP_SYS_ADMIN = placeholder();
+ public static final int CAP_SYS_BOOT = placeholder();
+ public static final int CAP_SYS_CHROOT = placeholder();
+ public static final int CAP_SYSLOG = placeholder();
+ public static final int CAP_SYS_MODULE = placeholder();
+ public static final int CAP_SYS_NICE = placeholder();
+ public static final int CAP_SYS_PACCT = placeholder();
+ public static final int CAP_SYS_PTRACE = placeholder();
+ public static final int CAP_SYS_RAWIO = placeholder();
+ public static final int CAP_SYS_RESOURCE = placeholder();
+ public static final int CAP_SYS_TIME = placeholder();
+ public static final int CAP_SYS_TTY_CONFIG = placeholder();
+ public static final int CAP_WAKE_ALARM = placeholder();
+ public static final int E2BIG = placeholder();
+ public static final int EACCES = placeholder();
+ public static final int EADDRINUSE = placeholder();
+ public static final int EADDRNOTAVAIL = placeholder();
+ public static final int EAFNOSUPPORT = placeholder();
+ public static final int EAGAIN = placeholder();
+ public static final int EAI_AGAIN = placeholder();
+ public static final int EAI_BADFLAGS = placeholder();
+ public static final int EAI_FAIL = placeholder();
+ public static final int EAI_FAMILY = placeholder();
+ public static final int EAI_MEMORY = placeholder();
+ public static final int EAI_NODATA = placeholder();
+ public static final int EAI_NONAME = placeholder();
+ public static final int EAI_OVERFLOW = placeholder();
+ public static final int EAI_SERVICE = placeholder();
+ public static final int EAI_SOCKTYPE = placeholder();
+ public static final int EAI_SYSTEM = placeholder();
+ public static final int EALREADY = placeholder();
+ public static final int EBADF = placeholder();
+ public static final int EBADMSG = placeholder();
+ public static final int EBUSY = placeholder();
+ public static final int ECANCELED = placeholder();
+ public static final int ECHILD = placeholder();
+ public static final int ECONNABORTED = placeholder();
+ public static final int ECONNREFUSED = placeholder();
+ public static final int ECONNRESET = placeholder();
+ public static final int EDEADLK = placeholder();
+ public static final int EDESTADDRREQ = placeholder();
+ public static final int EDOM = placeholder();
+ public static final int EDQUOT = placeholder();
+ public static final int EEXIST = placeholder();
+ public static final int EFAULT = placeholder();
+ public static final int EFBIG = placeholder();
+ public static final int EHOSTUNREACH = placeholder();
+ public static final int EIDRM = placeholder();
+ public static final int EILSEQ = placeholder();
+ public static final int EINPROGRESS = placeholder();
+ public static final int EINTR = placeholder();
+ public static final int EINVAL = placeholder();
+ public static final int EIO = placeholder();
+ public static final int EISCONN = placeholder();
+ public static final int EISDIR = placeholder();
+ public static final int ELOOP = placeholder();
+ public static final int EMFILE = placeholder();
+ public static final int EMLINK = placeholder();
+ public static final int EMSGSIZE = placeholder();
+ public static final int EMULTIHOP = placeholder();
+ public static final int ENAMETOOLONG = placeholder();
+ public static final int ENETDOWN = placeholder();
+ public static final int ENETRESET = placeholder();
+ public static final int ENETUNREACH = placeholder();
+ public static final int ENFILE = placeholder();
+ public static final int ENOBUFS = placeholder();
+ public static final int ENODATA = placeholder();
+ public static final int ENODEV = placeholder();
+ public static final int ENOENT = placeholder();
+ public static final int ENOEXEC = placeholder();
+ public static final int ENOLCK = placeholder();
+ public static final int ENOLINK = placeholder();
+ public static final int ENOMEM = placeholder();
+ public static final int ENOMSG = placeholder();
+ public static final int ENONET = placeholder();
+ public static final int ENOPROTOOPT = placeholder();
+ public static final int ENOSPC = placeholder();
+ public static final int ENOSR = placeholder();
+ public static final int ENOSTR = placeholder();
+ public static final int ENOSYS = placeholder();
+ public static final int ENOTCONN = placeholder();
+ public static final int ENOTDIR = placeholder();
+ public static final int ENOTEMPTY = placeholder();
+ public static final int ENOTSOCK = placeholder();
+ public static final int ENOTSUP = placeholder();
+ public static final int ENOTTY = placeholder();
+ public static final int ENXIO = placeholder();
+ public static final int EOPNOTSUPP = placeholder();
+ public static final int EOVERFLOW = placeholder();
+ public static final int EPERM = placeholder();
+ public static final int EPIPE = placeholder();
+ public static final int EPROTO = placeholder();
+ public static final int EPROTONOSUPPORT = placeholder();
+ public static final int EPROTOTYPE = placeholder();
+ public static final int ERANGE = placeholder();
+ public static final int EROFS = placeholder();
+ public static final int ESPIPE = placeholder();
+ public static final int ESRCH = placeholder();
+ public static final int ESTALE = placeholder();
+ public static final int ETH_P_ALL = placeholder();
+ public static final int ETH_P_ARP = placeholder();
+ public static final int ETH_P_IP = placeholder();
+ public static final int ETH_P_IPV6 = placeholder();
+ public static final int ETIME = placeholder();
+ public static final int ETIMEDOUT = placeholder();
+ public static final int ETXTBSY = placeholder();
+ /**
+ * "Too many users" error.
+ * See <a href="https://man7.org/linux/man-pages/man3/errno.3.html">errno(3)</a>.
+ *
+ * @hide
+ */
+// @UnsupportedAppUsage
+// @SystemApi(client = MODULE_LIBRARIES)
+ public static final int EUSERS = placeholder();
+ // On Linux, EWOULDBLOCK == EAGAIN. Use EAGAIN instead, to reduce confusion.
+ public static final int EXDEV = placeholder();
+ public static final int EXIT_FAILURE = placeholder();
+ public static final int EXIT_SUCCESS = placeholder();
+ public static final int FD_CLOEXEC = placeholder();
+ public static final int FIONREAD = placeholder();
+ public static final int F_DUPFD = placeholder();
+ public static final int F_DUPFD_CLOEXEC = placeholder();
+ public static final int F_GETFD = placeholder();
+ public static final int F_GETFL = placeholder();
+ public static final int F_GETLK = placeholder();
+ public static final int F_GETLK64 = placeholder();
+ public static final int F_GETOWN = placeholder();
+ public static final int F_OK = placeholder();
+ public static final int F_RDLCK = placeholder();
+ public static final int F_SETFD = placeholder();
+ public static final int F_SETFL = placeholder();
+ public static final int F_SETLK = placeholder();
+ public static final int F_SETLK64 = placeholder();
+ public static final int F_SETLKW = placeholder();
+ public static final int F_SETLKW64 = placeholder();
+ public static final int F_SETOWN = placeholder();
+ public static final int F_UNLCK = placeholder();
+ public static final int F_WRLCK = placeholder();
+ public static final int ICMP_ECHO = placeholder();
+ public static final int ICMP_ECHOREPLY = placeholder();
+ public static final int ICMP6_ECHO_REQUEST = placeholder();
+ public static final int ICMP6_ECHO_REPLY = placeholder();
+ public static final int IFA_F_DADFAILED = placeholder();
+ public static final int IFA_F_DEPRECATED = placeholder();
+ public static final int IFA_F_HOMEADDRESS = placeholder();
+ public static final int IFA_F_MANAGETEMPADDR = placeholder();
+ public static final int IFA_F_NODAD = placeholder();
+ public static final int IFA_F_NOPREFIXROUTE = placeholder();
+ public static final int IFA_F_OPTIMISTIC = placeholder();
+ public static final int IFA_F_PERMANENT = placeholder();
+ public static final int IFA_F_SECONDARY = placeholder();
+ public static final int IFA_F_TEMPORARY = placeholder();
+ public static final int IFA_F_TENTATIVE = placeholder();
+ public static final int IFF_ALLMULTI = placeholder();
+ public static final int IFF_AUTOMEDIA = placeholder();
+ public static final int IFF_BROADCAST = placeholder();
+ public static final int IFF_DEBUG = placeholder();
+ public static final int IFF_DYNAMIC = placeholder();
+ public static final int IFF_LOOPBACK = placeholder();
+ public static final int IFF_MASTER = placeholder();
+ public static final int IFF_MULTICAST = placeholder();
+ public static final int IFF_NOARP = placeholder();
+ public static final int IFF_NOTRAILERS = placeholder();
+ public static final int IFF_POINTOPOINT = placeholder();
+ public static final int IFF_PORTSEL = placeholder();
+ public static final int IFF_PROMISC = placeholder();
+ public static final int IFF_RUNNING = placeholder();
+ public static final int IFF_SLAVE = placeholder();
+ public static final int IFF_UP = placeholder();
+ public static final int IPPROTO_ICMP = placeholder();
+ public static final int IPPROTO_ICMPV6 = placeholder();
+ public static final int IPPROTO_IP = placeholder();
+ public static final int IPPROTO_IPV6 = placeholder();
+ public static final int IPPROTO_RAW = placeholder();
+ public static final int IPPROTO_TCP = placeholder();
+ public static final int IPPROTO_UDP = placeholder();
+
+ /**
+ * Encapsulation Security Payload protocol
+ *
+ * <p>Defined in /uapi/linux/in.h
+ */
+ public static final int IPPROTO_ESP = placeholder();
+
+ public static final int IPV6_CHECKSUM = placeholder();
+ public static final int IPV6_MULTICAST_HOPS = placeholder();
+ public static final int IPV6_MULTICAST_IF = placeholder();
+ public static final int IPV6_MULTICAST_LOOP = placeholder();
+ public static final int IPV6_PKTINFO = placeholder();
+ public static final int IPV6_RECVDSTOPTS = placeholder();
+ public static final int IPV6_RECVHOPLIMIT = placeholder();
+ public static final int IPV6_RECVHOPOPTS = placeholder();
+ public static final int IPV6_RECVPKTINFO = placeholder();
+ public static final int IPV6_RECVRTHDR = placeholder();
+ public static final int IPV6_RECVTCLASS = placeholder();
+ public static final int IPV6_TCLASS = placeholder();
+ public static final int IPV6_UNICAST_HOPS = placeholder();
+ public static final int IPV6_V6ONLY = placeholder();
+ /** @hide */
+// @UnsupportedAppUsage
+ public static final int IP_MULTICAST_ALL = placeholder();
+ public static final int IP_MULTICAST_IF = placeholder();
+ public static final int IP_MULTICAST_LOOP = placeholder();
+ public static final int IP_MULTICAST_TTL = placeholder();
+ /** @hide */
+// @UnsupportedAppUsage
+ public static final int IP_RECVTOS = placeholder();
+ public static final int IP_TOS = placeholder();
+ public static final int IP_TTL = placeholder();
+ /**
+ * Version constant to be used in {@link StructCapUserHeader} with
+ * {@link Os#capset(StructCapUserHeader, StructCapUserData[])} and
+ * {@link Os#capget(StructCapUserHeader)}.
+ *
+ * See <a href="https://man7.org/linux/man-pages/man2/capget.2.html">capget(2)</a>.
+ *
+ * @hide
+ */
+// @UnsupportedAppUsage
+// @SystemApi(client = MODULE_LIBRARIES)
+ public static final int _LINUX_CAPABILITY_VERSION_3 = placeholder();
+ public static final int MAP_FIXED = placeholder();
+ public static final int MAP_ANONYMOUS = placeholder();
+ /**
+ * Flag argument for {@code mmap(long, long, int, int, FileDescriptor, long)}.
+ *
+ * See <a href="http://man7.org/linux/man-pages/man2/mmap.2.html">mmap(2)</a>.
+ *
+ * @hide
+ */
+// @UnsupportedAppUsage
+// @SystemApi(client = MODULE_LIBRARIES)
+ public static final int MAP_POPULATE = placeholder();
+ public static final int MAP_PRIVATE = placeholder();
+ public static final int MAP_SHARED = placeholder();
+ public static final int MCAST_JOIN_GROUP = placeholder();
+ public static final int MCAST_LEAVE_GROUP = placeholder();
+ public static final int MCAST_JOIN_SOURCE_GROUP = placeholder();
+ public static final int MCAST_LEAVE_SOURCE_GROUP = placeholder();
+ public static final int MCAST_BLOCK_SOURCE = placeholder();
+ public static final int MCAST_UNBLOCK_SOURCE = placeholder();
+ public static final int MCL_CURRENT = placeholder();
+ public static final int MCL_FUTURE = placeholder();
+ public static final int MFD_CLOEXEC = placeholder();
+ public static final int MSG_CTRUNC = placeholder();
+ public static final int MSG_DONTROUTE = placeholder();
+ public static final int MSG_EOR = placeholder();
+ public static final int MSG_OOB = placeholder();
+ public static final int MSG_PEEK = placeholder();
+ public static final int MSG_TRUNC = placeholder();
+ public static final int MSG_WAITALL = placeholder();
+ public static final int MS_ASYNC = placeholder();
+ public static final int MS_INVALIDATE = placeholder();
+ public static final int MS_SYNC = placeholder();
+ public static final int NETLINK_NETFILTER = placeholder();
+ public static final int NETLINK_ROUTE = placeholder();
+ /**
+ * SELinux enforces that only system_server and netd may use this netlink socket type.
+ */
+ public static final int NETLINK_INET_DIAG = placeholder();
+
+ /**
+ * SELinux enforces that only system_server and netd may use this netlink socket type.
+ *
+ * @see <a href="https://man7.org/linux/man-pages/man7/netlink.7.html">netlink(7)</a>
+ */
+ public static final int NETLINK_XFRM = placeholder();
+
+ public static final int NI_DGRAM = placeholder();
+ public static final int NI_NAMEREQD = placeholder();
+ public static final int NI_NOFQDN = placeholder();
+ public static final int NI_NUMERICHOST = placeholder();
+ public static final int NI_NUMERICSERV = placeholder();
+ public static final int O_ACCMODE = placeholder();
+ public static final int O_APPEND = placeholder();
+ public static final int O_CLOEXEC = placeholder();
+ public static final int O_CREAT = placeholder();
+ /**
+ * Flag for {@code Os#open(String, int, int)}.
+ *
+ * When enabled, tries to minimize cache effects of the I/O to and from this
+ * file. In general this will degrade performance, but it is
+ * useful in special situations, such as when applications do
+ * their own caching. File I/O is done directly to/from
+ * user-space buffers. The {@link O_DIRECT} flag on its own makes an
+ * effort to transfer data synchronously, but does not give
+ * the guarantees of the {@link O_SYNC} flag that data and necessary
+ * metadata are transferred. To guarantee synchronous I/O,
+ * {@link O_SYNC} must be used in addition to {@link O_DIRECT}.
+ *
+ * See <a href="https://man7.org/linux/man-pages/man2/open.2.html">open(2)</a>.
+ *
+ * @hide
+ */
+// @UnsupportedAppUsage
+// @SystemApi(client = MODULE_LIBRARIES)
+ public static final int O_DIRECT = placeholder();
+ public static final int O_EXCL = placeholder();
+ public static final int O_NOCTTY = placeholder();
+ public static final int O_NOFOLLOW = placeholder();
+ public static final int O_NONBLOCK = placeholder();
+ public static final int O_RDONLY = placeholder();
+ public static final int O_RDWR = placeholder();
+ public static final int O_SYNC = placeholder();
+ public static final int O_DSYNC = placeholder();
+ public static final int O_TRUNC = placeholder();
+ public static final int O_WRONLY = placeholder();
+ public static final int POLLERR = placeholder();
+ public static final int POLLHUP = placeholder();
+ public static final int POLLIN = placeholder();
+ public static final int POLLNVAL = placeholder();
+ public static final int POLLOUT = placeholder();
+ public static final int POLLPRI = placeholder();
+ public static final int POLLRDBAND = placeholder();
+ public static final int POLLRDNORM = placeholder();
+ public static final int POLLWRBAND = placeholder();
+ public static final int POLLWRNORM = placeholder();
+ /**
+ * Reads or changes the ambient capability set of the calling thread.
+ * Has to be used as a first argument for {@link Os#prctl(int, long, long, long, long)}.
+ *
+ * See <a href="https://man7.org/linux/man-pages/man2/prctl.2.html">prctl(2)</a>.
+ *
+ * @hide
+ */
+// @UnsupportedAppUsage
+// @SystemApi(client = MODULE_LIBRARIES)
+ public static final int PR_CAP_AMBIENT = placeholder();
+ /**
+ * The capability specified in {@code arg3} of {@link Os#prctl(int, long, long, long, long)}
+ * is added to the ambient set. The specified capability must already
+ * be present in both the permitted and the inheritable sets of the process.
+ * Has to be used as a second argument for {@link Os#prctl(int, long, long, long, long)}.
+ *
+ * See <a href="https://man7.org/linux/man-pages/man2/prctl.2.html">prctl(2)</a>.
+ * @hide
+ */
+// @UnsupportedAppUsage
+// @SystemApi(client = MODULE_LIBRARIES)
+ public static final int PR_CAP_AMBIENT_RAISE = placeholder();
+ public static final int PR_GET_DUMPABLE = placeholder();
+ public static final int PR_SET_DUMPABLE = placeholder();
+ public static final int PR_SET_NO_NEW_PRIVS = placeholder();
+ public static final int PROT_EXEC = placeholder();
+ public static final int PROT_NONE = placeholder();
+ public static final int PROT_READ = placeholder();
+ public static final int PROT_WRITE = placeholder();
+ public static final int R_OK = placeholder();
+ /**
+ * Specifies a value one greater than the maximum file
+ * descriptor number that can be opened by this process.
+ *
+ * <p>Attempts ({@link Os#open(String, int, int)}, {@link Os#pipe()},
+ * {@link Os#dup(java.io.FileDescriptor)}, etc.) to exceed this
+ * limit yield the error {@link EMFILE}.
+ *
+ * See <a href="https://man7.org/linux/man-pages/man3/vlimit.3.html">getrlimit(2)</a>.
+ *
+ * @hide
+ */
+// @UnsupportedAppUsage
+// @SystemApi(client = MODULE_LIBRARIES)
+ public static final int RLIMIT_NOFILE = placeholder();
+ public static final int RT_SCOPE_HOST = placeholder();
+ public static final int RT_SCOPE_LINK = placeholder();
+ public static final int RT_SCOPE_NOWHERE = placeholder();
+ public static final int RT_SCOPE_SITE = placeholder();
+ public static final int RT_SCOPE_UNIVERSE = placeholder();
+ /**
+ * Bitmask for IPv4 addresses add/delete events multicast groups mask.
+ * Used in {@link NetlinkSocketAddress}.
+ *
+ * See <a href="https://man7.org/linux/man-pages/man7/netlink.7.html">netlink(7)</a>.
+ *
+ * @hide
+ */
+// @UnsupportedAppUsage
+// @SystemApi(client = MODULE_LIBRARIES)
+ public static final int RTMGRP_IPV4_IFADDR = placeholder();
+ /** @hide */
+// @UnsupportedAppUsage
+ public static final int RTMGRP_IPV4_MROUTE = placeholder();
+ /** @hide */
+// @UnsupportedAppUsage
+ public static final int RTMGRP_IPV4_ROUTE = placeholder();
+ /** @hide */
+// @UnsupportedAppUsage
+ public static final int RTMGRP_IPV4_RULE = placeholder();
+ /** @hide */
+// @UnsupportedAppUsage
+ public static final int RTMGRP_IPV6_IFADDR = placeholder();
+ /** @hide */
+// @UnsupportedAppUsage
+ public static final int RTMGRP_IPV6_IFINFO = placeholder();
+ /** @hide */
+// @UnsupportedAppUsage
+ public static final int RTMGRP_IPV6_MROUTE = placeholder();
+ /** @hide */
+// @UnsupportedAppUsage
+ public static final int RTMGRP_IPV6_PREFIX = placeholder();
+ /** @hide */
+// @UnsupportedAppUsage
+ public static final int RTMGRP_IPV6_ROUTE = placeholder();
+ /** @hide */
+// @UnsupportedAppUsage
+ public static final int RTMGRP_LINK = placeholder();
+ public static final int RTMGRP_NEIGH = placeholder();
+ /** @hide */
+// @UnsupportedAppUsage
+ public static final int RTMGRP_NOTIFY = placeholder();
+ /** @hide */
+// @UnsupportedAppUsage
+ public static final int RTMGRP_TC = placeholder();
+ public static final int SEEK_CUR = placeholder();
+ public static final int SEEK_END = placeholder();
+ public static final int SEEK_SET = placeholder();
+ public static final int SHUT_RD = placeholder();
+ public static final int SHUT_RDWR = placeholder();
+ public static final int SHUT_WR = placeholder();
+ public static final int SIGABRT = placeholder();
+ public static final int SIGALRM = placeholder();
+ public static final int SIGBUS = placeholder();
+ public static final int SIGCHLD = placeholder();
+ public static final int SIGCONT = placeholder();
+ public static final int SIGFPE = placeholder();
+ public static final int SIGHUP = placeholder();
+ public static final int SIGILL = placeholder();
+ public static final int SIGINT = placeholder();
+ public static final int SIGIO = placeholder();
+ public static final int SIGKILL = placeholder();
+ public static final int SIGPIPE = placeholder();
+ public static final int SIGPROF = placeholder();
+ public static final int SIGPWR = placeholder();
+ public static final int SIGQUIT = placeholder();
+ public static final int SIGRTMAX = placeholder();
+ public static final int SIGRTMIN = placeholder();
+ public static final int SIGSEGV = placeholder();
+ public static final int SIGSTKFLT = placeholder();
+ public static final int SIGSTOP = placeholder();
+ public static final int SIGSYS = placeholder();
+ public static final int SIGTERM = placeholder();
+ public static final int SIGTRAP = placeholder();
+ public static final int SIGTSTP = placeholder();
+ public static final int SIGTTIN = placeholder();
+ public static final int SIGTTOU = placeholder();
+ public static final int SIGURG = placeholder();
+ public static final int SIGUSR1 = placeholder();
+ public static final int SIGUSR2 = placeholder();
+ public static final int SIGVTALRM = placeholder();
+ public static final int SIGWINCH = placeholder();
+ public static final int SIGXCPU = placeholder();
+ public static final int SIGXFSZ = placeholder();
+ public static final int SIOCGIFADDR = placeholder();
+ public static final int SIOCGIFBRDADDR = placeholder();
+ public static final int SIOCGIFDSTADDR = placeholder();
+ public static final int SIOCGIFNETMASK = placeholder();
+
+ /**
+ * Set the close-on-exec ({@code FD_CLOEXEC}) flag on the new file
+ * descriptor created by {@link Os#socket(int,int,int)} or
+ * {@link Os#socketpair(int,int,int,java.io.FileDescriptor,java.io.FileDescriptor)}.
+ * See the description of the O_CLOEXEC flag in
+ * <a href="http://man7.org/linux/man-pages/man2/open.2.html">open(2)</a>
+ * for reasons why this may be useful.
+ *
+ * <p>Applications wishing to make use of this flag on older API versions
+ * may use {@link #O_CLOEXEC} instead. On Android, {@code O_CLOEXEC} and
+ * {@code SOCK_CLOEXEC} are the same value.
+ */
+ public static final int SOCK_CLOEXEC = placeholder();
+ public static final int SOCK_DGRAM = placeholder();
+
+ /**
+ * Set the O_NONBLOCK file status flag on the file descriptor
+ * created by {@link Os#socket(int,int,int)} or
+ * {@link Os#socketpair(int,int,int,java.io.FileDescriptor,java.io.FileDescriptor)}.
+ *
+ * <p>Applications wishing to make use of this flag on older API versions
+ * may use {@link #O_NONBLOCK} instead. On Android, {@code O_NONBLOCK}
+ * and {@code SOCK_NONBLOCK} are the same value.
+ */
+ public static final int SOCK_NONBLOCK = placeholder();
+ public static final int SOCK_RAW = placeholder();
+ public static final int SOCK_SEQPACKET = placeholder();
+ public static final int SOCK_STREAM = placeholder();
+ public static final int SOL_SOCKET = placeholder();
+ public static final int SOL_UDP = placeholder();
+ public static final int SOL_PACKET = placeholder();
+ public static final int SO_BINDTODEVICE = placeholder();
+ public static final int SO_BROADCAST = placeholder();
+ public static final int SO_DEBUG = placeholder();
+ /** @hide */
+// @UnsupportedAppUsage
+ public static final int SO_DOMAIN = placeholder();
+ public static final int SO_DONTROUTE = placeholder();
+ public static final int SO_ERROR = placeholder();
+ public static final int SO_KEEPALIVE = placeholder();
+ public static final int SO_LINGER = placeholder();
+ public static final int SO_OOBINLINE = placeholder();
+ public static final int SO_PASSCRED = placeholder();
+ public static final int SO_PEERCRED = placeholder();
+ /** @hide */
+// @UnsupportedAppUsage
+ public static final int SO_PROTOCOL = placeholder();
+ public static final int SO_RCVBUF = placeholder();
+ public static final int SO_RCVLOWAT = placeholder();
+ public static final int SO_RCVTIMEO = placeholder();
+ public static final int SO_REUSEADDR = placeholder();
+ public static final int SO_SNDBUF = placeholder();
+ public static final int SO_SNDLOWAT = placeholder();
+ public static final int SO_SNDTIMEO = placeholder();
+ public static final int SO_TYPE = placeholder();
+ public static final int PACKET_IGNORE_OUTGOING = placeholder();
+ /**
+ * Bitmask for flags argument of
+ * {@link splice(java.io.FileDescriptor, Int64Ref , FileDescriptor, Int64Ref, long, int)}.
+ *
+ * Attempt to move pages instead of copying. This is only a
+ * hint to the kernel: pages may still be copied if the
+ * kernel cannot move the pages from the pipe, or if the pipe
+ * buffers don't refer to full pages.
+ *
+ * See <a href="https://man7.org/linux/man-pages/man2/splice.2.html">splice(2)</a>.
+ *
+ * @hide
+ */
+// @UnsupportedAppUsage
+// @SystemApi(client = MODULE_LIBRARIES)
+ public static final int SPLICE_F_MOVE = placeholder();
+ /** @hide */
+// @UnsupportedAppUsage
+ public static final int SPLICE_F_NONBLOCK = placeholder();
+ /**
+ * Bitmask for flags argument of
+ * {@link splice(java.io.FileDescriptor, Int64Ref, FileDescriptor, Int64Ref, long, int)}.
+ *
+ * <p>Indicates that more data will be coming in a subsequent splice. This is
+ * a helpful hint when the {@code fdOut} refers to a socket.
+ *
+ * See <a href="https://man7.org/linux/man-pages/man2/splice.2.html">splice(2)</a>.
+ *
+ * @hide
+ */
+// @UnsupportedAppUsage
+// @SystemApi(client = MODULE_LIBRARIES)
+ public static final int SPLICE_F_MORE = placeholder();
+ public static final int STDERR_FILENO = placeholder();
+ public static final int STDIN_FILENO = placeholder();
+ public static final int STDOUT_FILENO = placeholder();
+ public static final int ST_MANDLOCK = placeholder();
+ public static final int ST_NOATIME = placeholder();
+ public static final int ST_NODEV = placeholder();
+ public static final int ST_NODIRATIME = placeholder();
+ public static final int ST_NOEXEC = placeholder();
+ public static final int ST_NOSUID = placeholder();
+ public static final int ST_RDONLY = placeholder();
+ public static final int ST_RELATIME = placeholder();
+ public static final int ST_SYNCHRONOUS = placeholder();
+ public static final int S_IFBLK = placeholder();
+ public static final int S_IFCHR = placeholder();
+ public static final int S_IFDIR = placeholder();
+ public static final int S_IFIFO = placeholder();
+ public static final int S_IFLNK = placeholder();
+ public static final int S_IFMT = placeholder();
+ public static final int S_IFREG = placeholder();
+ public static final int S_IFSOCK = placeholder();
+ public static final int S_IRGRP = placeholder();
+ public static final int S_IROTH = placeholder();
+ public static final int S_IRUSR = placeholder();
+ public static final int S_IRWXG = placeholder();
+ public static final int S_IRWXO = placeholder();
+ public static final int S_IRWXU = placeholder();
+ public static final int S_ISGID = placeholder();
+ public static final int S_ISUID = placeholder();
+ public static final int S_ISVTX = placeholder();
+ public static final int S_IWGRP = placeholder();
+ public static final int S_IWOTH = placeholder();
+ public static final int S_IWUSR = placeholder();
+ public static final int S_IXGRP = placeholder();
+ public static final int S_IXOTH = placeholder();
+ public static final int S_IXUSR = placeholder();
+ public static final int TCP_NODELAY = placeholder();
+ public static final int TCP_USER_TIMEOUT = placeholder();
+ public static final int UDP_GRO = placeholder();
+ public static final int UDP_SEGMENT = placeholder();
+ /**
+ * Get the number of bytes in the output buffer.
+ *
+ * See <a href="https://man7.org/linux/man-pages/man2/ioctl.2.html">ioctl(2)</a>.
+ *
+ * @hide
+ */
+// @UnsupportedAppUsage
+// @SystemApi(client = MODULE_LIBRARIES)
+ public static final int TIOCOUTQ = placeholder();
+ /**
+ * Sockopt option to encapsulate ESP packets in UDP.
+ *
+ * @hide
+ */
+// @UnsupportedAppUsage
+// @SystemApi(client = MODULE_LIBRARIES)
+ public static final int UDP_ENCAP = placeholder();
+ /** @hide */
+// @UnsupportedAppUsage
+ public static final int UDP_ENCAP_ESPINUDP_NON_IKE = placeholder();
+ /** @hide */
+// @UnsupportedAppUsage
+// @SystemApi(client = MODULE_LIBRARIES)
+ public static final int UDP_ENCAP_ESPINUDP = placeholder();
+ /** @hide */
+// @UnsupportedAppUsage
+ public static final int UNIX_PATH_MAX = placeholder();
+ public static final int WCONTINUED = placeholder();
+ public static final int WEXITED = placeholder();
+ public static final int WNOHANG = placeholder();
+ public static final int WNOWAIT = placeholder();
+ public static final int WSTOPPED = placeholder();
+ public static final int WUNTRACED = placeholder();
+ public static final int W_OK = placeholder();
+ /**
+ * {@code flags} option for {@link Os#setxattr(String, String, byte[], int)}.
+ *
+ * <p>Performs a pure create, which fails if the named attribute exists already.
+ *
+ * See <a href="http://man7.org/linux/man-pages/man2/setxattr.2.html">setxattr(2)</a>.
+ *
+ * @hide
+ */
+// @UnsupportedAppUsage
+// @SystemApi(client = MODULE_LIBRARIES)
+ public static final int XATTR_CREATE = placeholder();
+ /**
+ * {@code flags} option for {@link Os#setxattr(String, String, byte[], int)}.
+ *
+ * <p>Perform a pure replace operation, which fails if the named attribute
+ * does not already exist.
+ *
+ * See <a href="http://man7.org/linux/man-pages/man2/setxattr.2.html">setxattr(2)</a>.
+ *
+ * @hide
+ */
+// @UnsupportedAppUsage
+// @SystemApi(client = MODULE_LIBRARIES)
+ public static final int XATTR_REPLACE = placeholder();
+ public static final int X_OK = placeholder();
+ public static final int _SC_2_CHAR_TERM = placeholder();
+ public static final int _SC_2_C_BIND = placeholder();
+ public static final int _SC_2_C_DEV = placeholder();
+ public static final int _SC_2_C_VERSION = placeholder();
+ public static final int _SC_2_FORT_DEV = placeholder();
+ public static final int _SC_2_FORT_RUN = placeholder();
+ public static final int _SC_2_LOCALEDEF = placeholder();
+ public static final int _SC_2_SW_DEV = placeholder();
+ public static final int _SC_2_UPE = placeholder();
+ public static final int _SC_2_VERSION = placeholder();
+ public static final int _SC_AIO_LISTIO_MAX = placeholder();
+ public static final int _SC_AIO_MAX = placeholder();
+ public static final int _SC_AIO_PRIO_DELTA_MAX = placeholder();
+ public static final int _SC_ARG_MAX = placeholder();
+ public static final int _SC_ASYNCHRONOUS_IO = placeholder();
+ public static final int _SC_ATEXIT_MAX = placeholder();
+ public static final int _SC_AVPHYS_PAGES = placeholder();
+ public static final int _SC_BC_BASE_MAX = placeholder();
+ public static final int _SC_BC_DIM_MAX = placeholder();
+ public static final int _SC_BC_SCALE_MAX = placeholder();
+ public static final int _SC_BC_STRING_MAX = placeholder();
+ public static final int _SC_CHILD_MAX = placeholder();
+ public static final int _SC_CLK_TCK = placeholder();
+ public static final int _SC_COLL_WEIGHTS_MAX = placeholder();
+ public static final int _SC_DELAYTIMER_MAX = placeholder();
+ public static final int _SC_EXPR_NEST_MAX = placeholder();
+ public static final int _SC_FSYNC = placeholder();
+ public static final int _SC_GETGR_R_SIZE_MAX = placeholder();
+ public static final int _SC_GETPW_R_SIZE_MAX = placeholder();
+ public static final int _SC_IOV_MAX = placeholder();
+ public static final int _SC_JOB_CONTROL = placeholder();
+ public static final int _SC_LINE_MAX = placeholder();
+ public static final int _SC_LOGIN_NAME_MAX = placeholder();
+ public static final int _SC_MAPPED_FILES = placeholder();
+ public static final int _SC_MEMLOCK = placeholder();
+ public static final int _SC_MEMLOCK_RANGE = placeholder();
+ public static final int _SC_MEMORY_PROTECTION = placeholder();
+ public static final int _SC_MESSAGE_PASSING = placeholder();
+ public static final int _SC_MQ_OPEN_MAX = placeholder();
+ public static final int _SC_MQ_PRIO_MAX = placeholder();
+ public static final int _SC_NGROUPS_MAX = placeholder();
+ public static final int _SC_NPROCESSORS_CONF = placeholder();
+ public static final int _SC_NPROCESSORS_ONLN = placeholder();
+ public static final int _SC_OPEN_MAX = placeholder();
+ public static final int _SC_PAGESIZE = placeholder();
+ public static final int _SC_PAGE_SIZE = placeholder();
+ public static final int _SC_PASS_MAX = placeholder();
+ public static final int _SC_PHYS_PAGES = placeholder();
+ public static final int _SC_PRIORITIZED_IO = placeholder();
+ public static final int _SC_PRIORITY_SCHEDULING = placeholder();
+ public static final int _SC_REALTIME_SIGNALS = placeholder();
+ public static final int _SC_RE_DUP_MAX = placeholder();
+ public static final int _SC_RTSIG_MAX = placeholder();
+ public static final int _SC_SAVED_IDS = placeholder();
+ public static final int _SC_SEMAPHORES = placeholder();
+ public static final int _SC_SEM_NSEMS_MAX = placeholder();
+ public static final int _SC_SEM_VALUE_MAX = placeholder();
+ public static final int _SC_SHARED_MEMORY_OBJECTS = placeholder();
+ public static final int _SC_SIGQUEUE_MAX = placeholder();
+ public static final int _SC_STREAM_MAX = placeholder();
+ public static final int _SC_SYNCHRONIZED_IO = placeholder();
+ public static final int _SC_THREADS = placeholder();
+ public static final int _SC_THREAD_ATTR_STACKADDR = placeholder();
+ public static final int _SC_THREAD_ATTR_STACKSIZE = placeholder();
+ public static final int _SC_THREAD_DESTRUCTOR_ITERATIONS = placeholder();
+ public static final int _SC_THREAD_KEYS_MAX = placeholder();
+ public static final int _SC_THREAD_PRIORITY_SCHEDULING = placeholder();
+ public static final int _SC_THREAD_PRIO_INHERIT = placeholder();
+ public static final int _SC_THREAD_PRIO_PROTECT = placeholder();
+ public static final int _SC_THREAD_SAFE_FUNCTIONS = placeholder();
+ public static final int _SC_THREAD_STACK_MIN = placeholder();
+ public static final int _SC_THREAD_THREADS_MAX = placeholder();
+ public static final int _SC_TIMERS = placeholder();
+ public static final int _SC_TIMER_MAX = placeholder();
+ public static final int _SC_TTY_NAME_MAX = placeholder();
+ public static final int _SC_TZNAME_MAX = placeholder();
+ public static final int _SC_VERSION = placeholder();
+ public static final int _SC_XBS5_ILP32_OFF32 = placeholder();
+ public static final int _SC_XBS5_ILP32_OFFBIG = placeholder();
+ public static final int _SC_XBS5_LP64_OFF64 = placeholder();
+ public static final int _SC_XBS5_LPBIG_OFFBIG = placeholder();
+ public static final int _SC_XOPEN_CRYPT = placeholder();
+ public static final int _SC_XOPEN_ENH_I18N = placeholder();
+ public static final int _SC_XOPEN_LEGACY = placeholder();
+ public static final int _SC_XOPEN_REALTIME = placeholder();
+ public static final int _SC_XOPEN_REALTIME_THREADS = placeholder();
+ public static final int _SC_XOPEN_SHM = placeholder();
+ public static final int _SC_XOPEN_UNIX = placeholder();
+ public static final int _SC_XOPEN_VERSION = placeholder();
+ public static final int _SC_XOPEN_XCU_VERSION = placeholder();
+
+ /**
+ * Returns the string name of a getaddrinfo(3) error value.
+ * For example, "EAI_AGAIN".
+ */
+ public static String gaiName(int error) {
+ if (error == EAI_AGAIN) {
+ return "EAI_AGAIN";
+ }
+ if (error == EAI_BADFLAGS) {
+ return "EAI_BADFLAGS";
+ }
+ if (error == EAI_FAIL) {
+ return "EAI_FAIL";
+ }
+ if (error == EAI_FAMILY) {
+ return "EAI_FAMILY";
+ }
+ if (error == EAI_MEMORY) {
+ return "EAI_MEMORY";
+ }
+ if (error == EAI_NODATA) {
+ return "EAI_NODATA";
+ }
+ if (error == EAI_NONAME) {
+ return "EAI_NONAME";
+ }
+ if (error == EAI_OVERFLOW) {
+ return "EAI_OVERFLOW";
+ }
+ if (error == EAI_SERVICE) {
+ return "EAI_SERVICE";
+ }
+ if (error == EAI_SOCKTYPE) {
+ return "EAI_SOCKTYPE";
+ }
+ if (error == EAI_SYSTEM) {
+ return "EAI_SYSTEM";
+ }
+ return null;
+ }
+
+ /**
+ * Returns the string name of an errno value.
+ * For example, "EACCES". See {@link Os#strerror} for human-readable errno descriptions.
+ */
+ public static String errnoName(int errno) {
+ if (errno == E2BIG) {
+ return "E2BIG";
+ }
+ if (errno == EACCES) {
+ return "EACCES";
+ }
+ if (errno == EADDRINUSE) {
+ return "EADDRINUSE";
+ }
+ if (errno == EADDRNOTAVAIL) {
+ return "EADDRNOTAVAIL";
+ }
+ if (errno == EAFNOSUPPORT) {
+ return "EAFNOSUPPORT";
+ }
+ if (errno == EAGAIN) {
+ return "EAGAIN";
+ }
+ if (errno == EALREADY) {
+ return "EALREADY";
+ }
+ if (errno == EBADF) {
+ return "EBADF";
+ }
+ if (errno == EBADMSG) {
+ return "EBADMSG";
+ }
+ if (errno == EBUSY) {
+ return "EBUSY";
+ }
+ if (errno == ECANCELED) {
+ return "ECANCELED";
+ }
+ if (errno == ECHILD) {
+ return "ECHILD";
+ }
+ if (errno == ECONNABORTED) {
+ return "ECONNABORTED";
+ }
+ if (errno == ECONNREFUSED) {
+ return "ECONNREFUSED";
+ }
+ if (errno == ECONNRESET) {
+ return "ECONNRESET";
+ }
+ if (errno == EDEADLK) {
+ return "EDEADLK";
+ }
+ if (errno == EDESTADDRREQ) {
+ return "EDESTADDRREQ";
+ }
+ if (errno == EDOM) {
+ return "EDOM";
+ }
+ if (errno == EDQUOT) {
+ return "EDQUOT";
+ }
+ if (errno == EEXIST) {
+ return "EEXIST";
+ }
+ if (errno == EFAULT) {
+ return "EFAULT";
+ }
+ if (errno == EFBIG) {
+ return "EFBIG";
+ }
+ if (errno == EHOSTUNREACH) {
+ return "EHOSTUNREACH";
+ }
+ if (errno == EIDRM) {
+ return "EIDRM";
+ }
+ if (errno == EILSEQ) {
+ return "EILSEQ";
+ }
+ if (errno == EINPROGRESS) {
+ return "EINPROGRESS";
+ }
+ if (errno == EINTR) {
+ return "EINTR";
+ }
+ if (errno == EINVAL) {
+ return "EINVAL";
+ }
+ if (errno == EIO) {
+ return "EIO";
+ }
+ if (errno == EISCONN) {
+ return "EISCONN";
+ }
+ if (errno == EISDIR) {
+ return "EISDIR";
+ }
+ if (errno == ELOOP) {
+ return "ELOOP";
+ }
+ if (errno == EMFILE) {
+ return "EMFILE";
+ }
+ if (errno == EMLINK) {
+ return "EMLINK";
+ }
+ if (errno == EMSGSIZE) {
+ return "EMSGSIZE";
+ }
+ if (errno == EMULTIHOP) {
+ return "EMULTIHOP";
+ }
+ if (errno == ENAMETOOLONG) {
+ return "ENAMETOOLONG";
+ }
+ if (errno == ENETDOWN) {
+ return "ENETDOWN";
+ }
+ if (errno == ENETRESET) {
+ return "ENETRESET";
+ }
+ if (errno == ENETUNREACH) {
+ return "ENETUNREACH";
+ }
+ if (errno == ENFILE) {
+ return "ENFILE";
+ }
+ if (errno == ENOBUFS) {
+ return "ENOBUFS";
+ }
+ if (errno == ENODATA) {
+ return "ENODATA";
+ }
+ if (errno == ENODEV) {
+ return "ENODEV";
+ }
+ if (errno == ENOENT) {
+ return "ENOENT";
+ }
+ if (errno == ENOEXEC) {
+ return "ENOEXEC";
+ }
+ if (errno == ENOLCK) {
+ return "ENOLCK";
+ }
+ if (errno == ENOLINK) {
+ return "ENOLINK";
+ }
+ if (errno == ENOMEM) {
+ return "ENOMEM";
+ }
+ if (errno == ENOMSG) {
+ return "ENOMSG";
+ }
+ if (errno == ENONET) {
+ return "ENONET";
+ }
+ if (errno == ENOPROTOOPT) {
+ return "ENOPROTOOPT";
+ }
+ if (errno == ENOSPC) {
+ return "ENOSPC";
+ }
+ if (errno == ENOSR) {
+ return "ENOSR";
+ }
+ if (errno == ENOSTR) {
+ return "ENOSTR";
+ }
+ if (errno == ENOSYS) {
+ return "ENOSYS";
+ }
+ if (errno == ENOTCONN) {
+ return "ENOTCONN";
+ }
+ if (errno == ENOTDIR) {
+ return "ENOTDIR";
+ }
+ if (errno == ENOTEMPTY) {
+ return "ENOTEMPTY";
+ }
+ if (errno == ENOTSOCK) {
+ return "ENOTSOCK";
+ }
+ if (errno == ENOTSUP) {
+ return "ENOTSUP";
+ }
+ if (errno == ENOTTY) {
+ return "ENOTTY";
+ }
+ if (errno == ENXIO) {
+ return "ENXIO";
+ }
+ if (errno == EOPNOTSUPP) {
+ return "EOPNOTSUPP";
+ }
+ if (errno == EOVERFLOW) {
+ return "EOVERFLOW";
+ }
+ if (errno == EPERM) {
+ return "EPERM";
+ }
+ if (errno == EPIPE) {
+ return "EPIPE";
+ }
+ if (errno == EPROTO) {
+ return "EPROTO";
+ }
+ if (errno == EPROTONOSUPPORT) {
+ return "EPROTONOSUPPORT";
+ }
+ if (errno == EPROTOTYPE) {
+ return "EPROTOTYPE";
+ }
+ if (errno == ERANGE) {
+ return "ERANGE";
+ }
+ if (errno == EROFS) {
+ return "EROFS";
+ }
+ if (errno == ESPIPE) {
+ return "ESPIPE";
+ }
+ if (errno == ESRCH) {
+ return "ESRCH";
+ }
+ if (errno == ESTALE) {
+ return "ESTALE";
+ }
+ if (errno == ETIME) {
+ return "ETIME";
+ }
+ if (errno == ETIMEDOUT) {
+ return "ETIMEDOUT";
+ }
+ if (errno == ETXTBSY) {
+ return "ETXTBSY";
+ }
+ if (errno == EXDEV) {
+ return "EXDEV";
+ }
+ return null;
+ }
+
+ // [ravenwood-change] Moved to a nested class.
+ // @UnsupportedAppUsage
+ static class Native {
+ private static native void initConstants();
+ }
+
+ // A hack to avoid these constants being inlined by javac...
+// @UnsupportedAppUsage
+ private static int placeholder() { return 0; }
+ // ...because we want to initialize them at runtime.
+ static {
+ // [ravenwood-change] Load the JNI lib.
+ RavenwoodCommonUtils.loadRavenwoodNativeRuntime();
+ Native.initConstants();
+ }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/ravenwood/LibcoreRavenwoodUtils.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/ravenwood/LibcoreRavenwoodUtils.java
deleted file mode 100644
index 839b62a..0000000
--- a/ravenwood/runtime-helper-src/libcore-fake/libcore/ravenwood/LibcoreRavenwoodUtils.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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 libcore.ravenwood;
-
-public class LibcoreRavenwoodUtils {
- private LibcoreRavenwoodUtils() {
- }
-
- public static void loadRavenwoodNativeRuntime() {
- // TODO Stop using reflections.
- // We need to call RavenwoodUtils.loadRavenwoodNativeRuntime(), but due to the build
- // structure complexity, we can't refer to to this method directly from here,
- // so let's use reflections for now...
- try {
- final var clazz = Class.forName("android.platform.test.ravenwood.RavenwoodUtils");
- final var method = clazz.getMethod("loadRavenwoodNativeRuntime");
- method.invoke(null);
- } catch (Throwable th) {
- throw new IllegalStateException("Failed to load Ravenwood native runtime", th);
- }
- }
-}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
index 93861e8..14b5a4f 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
@@ -15,7 +15,7 @@
*/
package libcore.util;
-import libcore.ravenwood.LibcoreRavenwoodUtils;
+import com.android.ravenwood.common.RavenwoodRuntimeNative;
import java.lang.ref.Cleaner;
import java.lang.ref.Reference;
@@ -27,11 +27,6 @@
* (Should ART switch to java.lang.ref.Cleaner?)
*/
public class NativeAllocationRegistry {
- static {
- // Initialize the JNI method.
- LibcoreRavenwoodUtils.loadRavenwoodNativeRuntime();
- }
-
private final long mFreeFunction;
private static final Cleaner sCleaner = Cleaner.create();
@@ -71,7 +66,7 @@
}
final Runnable releaser = () -> {
- applyFreeFunction(mFreeFunction, nativePtr);
+ RavenwoodRuntimeNative.applyFreeFunction(mFreeFunction, nativePtr);
};
sCleaner.register(referent, releaser);
@@ -79,10 +74,4 @@
Reference.reachabilityFence(referent);
return releaser;
}
-
- /**
- * Calls {@code freeFunction}({@code nativePtr}).
- */
- public static native void applyFreeFunction(long freeFunction, long nativePtr);
}
-
diff --git a/ravenwood/runtime-jni/ravenwood_os_constants.cpp b/ravenwood/runtime-jni/ravenwood_os_constants.cpp
new file mode 100644
index 0000000..ea6c9d4
--- /dev/null
+++ b/ravenwood/runtime-jni/ravenwood_os_constants.cpp
@@ -0,0 +1,766 @@
+/*
+ * 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.
+ */
+
+// Copied from libcore/luni/src/main/native/android_system_OsConstants.cpp,
+// changes annotated with [ravenwood-change].
+
+#define LOG_TAG "OsConstants"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/tcp.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <unistd.h>
+
+#include <net/if_arp.h>
+#include <linux/if_ether.h>
+
+// After the others because these are not necessarily self-contained in glibc.
+#include <linux/if_addr.h>
+#include <linux/rtnetlink.h>
+
+// Include linux socket constants for setting sockopts
+#include <linux/udp.h>
+
+#include <net/if.h> // After <sys/socket.h> to work around a Mac header file bug.
+
+// [ravenwood-change] always include it
+// #if defined(__BIONIC__)
+#include <linux/capability.h>
+// #endif
+
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/jni_macros.h>
+
+// [ravenwood-change] -- can't access "Portability.h", so just inline it here.
+// #include "Portability.h"
+#include <byteswap.h>
+#include <sys/sendfile.h>
+#include <sys/statvfs.h>
+#include <netdb.h>
+#include <linux/vm_sockets.h>
+
+// For LOG_ALWAYS_FATAL_IF
+#include "utils/Log.h"
+
+
+static void initConstant(JNIEnv* env, jclass c, const char* fieldName, int value) {
+ jfieldID field = env->GetStaticFieldID(c, fieldName, "I");
+ env->SetStaticIntField(c, field, value);
+}
+
+static void OsConstants_initConstants(JNIEnv* env, jclass) {
+ // [ravenwood-change] -- the constants are in the outer class, but this JNI method is in the
+ // nested class, so we need to get the outer class here.
+ jclass c = env->FindClass("android/system/OsConstants");
+ LOG_ALWAYS_FATAL_IF(c == NULL, "Unable to find class android/system/OsConstants");
+
+ initConstant(env, c, "AF_INET", AF_INET);
+ initConstant(env, c, "AF_INET6", AF_INET6);
+ initConstant(env, c, "AF_PACKET", AF_PACKET);
+ initConstant(env, c, "AF_NETLINK", AF_NETLINK);
+ initConstant(env, c, "AF_UNIX", AF_UNIX);
+ initConstant(env, c, "AF_VSOCK", AF_VSOCK);
+ initConstant(env, c, "AF_UNSPEC", AF_UNSPEC);
+ initConstant(env, c, "AI_ADDRCONFIG", AI_ADDRCONFIG);
+ initConstant(env, c, "AI_ALL", AI_ALL);
+ initConstant(env, c, "AI_CANONNAME", AI_CANONNAME);
+ initConstant(env, c, "AI_NUMERICHOST", AI_NUMERICHOST);
+#if defined(AI_NUMERICSERV)
+ initConstant(env, c, "AI_NUMERICSERV", AI_NUMERICSERV);
+#endif
+ initConstant(env, c, "AI_PASSIVE", AI_PASSIVE);
+ initConstant(env, c, "AI_V4MAPPED", AI_V4MAPPED);
+ initConstant(env, c, "ARPHRD_ETHER", ARPHRD_ETHER);
+ initConstant(env, c, "VMADDR_PORT_ANY", VMADDR_PORT_ANY);
+ initConstant(env, c, "VMADDR_CID_ANY", VMADDR_CID_ANY);
+ initConstant(env, c, "VMADDR_CID_LOCAL", VMADDR_CID_LOCAL);
+ initConstant(env, c, "VMADDR_CID_HOST", VMADDR_CID_HOST);
+ initConstant(env, c, "ARPHRD_LOOPBACK", ARPHRD_LOOPBACK);
+#if defined(CAP_LAST_CAP)
+ initConstant(env, c, "CAP_AUDIT_CONTROL", CAP_AUDIT_CONTROL);
+ initConstant(env, c, "CAP_AUDIT_WRITE", CAP_AUDIT_WRITE);
+ initConstant(env, c, "CAP_BLOCK_SUSPEND", CAP_BLOCK_SUSPEND);
+ initConstant(env, c, "CAP_CHOWN", CAP_CHOWN);
+ initConstant(env, c, "CAP_DAC_OVERRIDE", CAP_DAC_OVERRIDE);
+ initConstant(env, c, "CAP_DAC_READ_SEARCH", CAP_DAC_READ_SEARCH);
+ initConstant(env, c, "CAP_FOWNER", CAP_FOWNER);
+ initConstant(env, c, "CAP_FSETID", CAP_FSETID);
+ initConstant(env, c, "CAP_IPC_LOCK", CAP_IPC_LOCK);
+ initConstant(env, c, "CAP_IPC_OWNER", CAP_IPC_OWNER);
+ initConstant(env, c, "CAP_KILL", CAP_KILL);
+ initConstant(env, c, "CAP_LAST_CAP", CAP_LAST_CAP);
+ initConstant(env, c, "CAP_LEASE", CAP_LEASE);
+ initConstant(env, c, "CAP_LINUX_IMMUTABLE", CAP_LINUX_IMMUTABLE);
+ initConstant(env, c, "CAP_MAC_ADMIN", CAP_MAC_ADMIN);
+ initConstant(env, c, "CAP_MAC_OVERRIDE", CAP_MAC_OVERRIDE);
+ initConstant(env, c, "CAP_MKNOD", CAP_MKNOD);
+ initConstant(env, c, "CAP_NET_ADMIN", CAP_NET_ADMIN);
+ initConstant(env, c, "CAP_NET_BIND_SERVICE", CAP_NET_BIND_SERVICE);
+ initConstant(env, c, "CAP_NET_BROADCAST", CAP_NET_BROADCAST);
+ initConstant(env, c, "CAP_NET_RAW", CAP_NET_RAW);
+ initConstant(env, c, "CAP_SETFCAP", CAP_SETFCAP);
+ initConstant(env, c, "CAP_SETGID", CAP_SETGID);
+ initConstant(env, c, "CAP_SETPCAP", CAP_SETPCAP);
+ initConstant(env, c, "CAP_SETUID", CAP_SETUID);
+ initConstant(env, c, "CAP_SYS_ADMIN", CAP_SYS_ADMIN);
+ initConstant(env, c, "CAP_SYS_BOOT", CAP_SYS_BOOT);
+ initConstant(env, c, "CAP_SYS_CHROOT", CAP_SYS_CHROOT);
+ initConstant(env, c, "CAP_SYSLOG", CAP_SYSLOG);
+ initConstant(env, c, "CAP_SYS_MODULE", CAP_SYS_MODULE);
+ initConstant(env, c, "CAP_SYS_NICE", CAP_SYS_NICE);
+ initConstant(env, c, "CAP_SYS_PACCT", CAP_SYS_PACCT);
+ initConstant(env, c, "CAP_SYS_PTRACE", CAP_SYS_PTRACE);
+ initConstant(env, c, "CAP_SYS_RAWIO", CAP_SYS_RAWIO);
+ initConstant(env, c, "CAP_SYS_RESOURCE", CAP_SYS_RESOURCE);
+ initConstant(env, c, "CAP_SYS_TIME", CAP_SYS_TIME);
+ initConstant(env, c, "CAP_SYS_TTY_CONFIG", CAP_SYS_TTY_CONFIG);
+ initConstant(env, c, "CAP_WAKE_ALARM", CAP_WAKE_ALARM);
+#endif
+ initConstant(env, c, "E2BIG", E2BIG);
+ initConstant(env, c, "EACCES", EACCES);
+ initConstant(env, c, "EADDRINUSE", EADDRINUSE);
+ initConstant(env, c, "EADDRNOTAVAIL", EADDRNOTAVAIL);
+ initConstant(env, c, "EAFNOSUPPORT", EAFNOSUPPORT);
+ initConstant(env, c, "EAGAIN", EAGAIN);
+ initConstant(env, c, "EAI_AGAIN", EAI_AGAIN);
+ initConstant(env, c, "EAI_BADFLAGS", EAI_BADFLAGS);
+ initConstant(env, c, "EAI_FAIL", EAI_FAIL);
+ initConstant(env, c, "EAI_FAMILY", EAI_FAMILY);
+ initConstant(env, c, "EAI_MEMORY", EAI_MEMORY);
+ initConstant(env, c, "EAI_NODATA", EAI_NODATA);
+ initConstant(env, c, "EAI_NONAME", EAI_NONAME);
+#if defined(EAI_OVERFLOW)
+ initConstant(env, c, "EAI_OVERFLOW", EAI_OVERFLOW);
+#endif
+ initConstant(env, c, "EAI_SERVICE", EAI_SERVICE);
+ initConstant(env, c, "EAI_SOCKTYPE", EAI_SOCKTYPE);
+ initConstant(env, c, "EAI_SYSTEM", EAI_SYSTEM);
+ initConstant(env, c, "EALREADY", EALREADY);
+ initConstant(env, c, "EBADF", EBADF);
+ initConstant(env, c, "EBADMSG", EBADMSG);
+ initConstant(env, c, "EBUSY", EBUSY);
+ initConstant(env, c, "ECANCELED", ECANCELED);
+ initConstant(env, c, "ECHILD", ECHILD);
+ initConstant(env, c, "ECONNABORTED", ECONNABORTED);
+ initConstant(env, c, "ECONNREFUSED", ECONNREFUSED);
+ initConstant(env, c, "ECONNRESET", ECONNRESET);
+ initConstant(env, c, "EDEADLK", EDEADLK);
+ initConstant(env, c, "EDESTADDRREQ", EDESTADDRREQ);
+ initConstant(env, c, "EDOM", EDOM);
+ initConstant(env, c, "EDQUOT", EDQUOT);
+ initConstant(env, c, "EEXIST", EEXIST);
+ initConstant(env, c, "EFAULT", EFAULT);
+ initConstant(env, c, "EFBIG", EFBIG);
+ initConstant(env, c, "EHOSTUNREACH", EHOSTUNREACH);
+ initConstant(env, c, "EIDRM", EIDRM);
+ initConstant(env, c, "EILSEQ", EILSEQ);
+ initConstant(env, c, "EINPROGRESS", EINPROGRESS);
+ initConstant(env, c, "EINTR", EINTR);
+ initConstant(env, c, "EINVAL", EINVAL);
+ initConstant(env, c, "EIO", EIO);
+ initConstant(env, c, "EISCONN", EISCONN);
+ initConstant(env, c, "EISDIR", EISDIR);
+ initConstant(env, c, "ELOOP", ELOOP);
+ initConstant(env, c, "EMFILE", EMFILE);
+ initConstant(env, c, "EMLINK", EMLINK);
+ initConstant(env, c, "EMSGSIZE", EMSGSIZE);
+ initConstant(env, c, "EMULTIHOP", EMULTIHOP);
+ initConstant(env, c, "ENAMETOOLONG", ENAMETOOLONG);
+ initConstant(env, c, "ENETDOWN", ENETDOWN);
+ initConstant(env, c, "ENETRESET", ENETRESET);
+ initConstant(env, c, "ENETUNREACH", ENETUNREACH);
+ initConstant(env, c, "ENFILE", ENFILE);
+ initConstant(env, c, "ENOBUFS", ENOBUFS);
+ initConstant(env, c, "ENODATA", ENODATA);
+ initConstant(env, c, "ENODEV", ENODEV);
+ initConstant(env, c, "ENOENT", ENOENT);
+ initConstant(env, c, "ENOEXEC", ENOEXEC);
+ initConstant(env, c, "ENOLCK", ENOLCK);
+ initConstant(env, c, "ENOLINK", ENOLINK);
+ initConstant(env, c, "ENOMEM", ENOMEM);
+ initConstant(env, c, "ENOMSG", ENOMSG);
+ initConstant(env, c, "ENONET", ENONET);
+ initConstant(env, c, "ENOPROTOOPT", ENOPROTOOPT);
+ initConstant(env, c, "ENOSPC", ENOSPC);
+ initConstant(env, c, "ENOSR", ENOSR);
+ initConstant(env, c, "ENOSTR", ENOSTR);
+ initConstant(env, c, "ENOSYS", ENOSYS);
+ initConstant(env, c, "ENOTCONN", ENOTCONN);
+ initConstant(env, c, "ENOTDIR", ENOTDIR);
+ initConstant(env, c, "ENOTEMPTY", ENOTEMPTY);
+ initConstant(env, c, "ENOTSOCK", ENOTSOCK);
+ initConstant(env, c, "ENOTSUP", ENOTSUP);
+ initConstant(env, c, "ENOTTY", ENOTTY);
+ initConstant(env, c, "ENXIO", ENXIO);
+ initConstant(env, c, "EOPNOTSUPP", EOPNOTSUPP);
+ initConstant(env, c, "EOVERFLOW", EOVERFLOW);
+ initConstant(env, c, "EPERM", EPERM);
+ initConstant(env, c, "EPIPE", EPIPE);
+ initConstant(env, c, "EPROTO", EPROTO);
+ initConstant(env, c, "EPROTONOSUPPORT", EPROTONOSUPPORT);
+ initConstant(env, c, "EPROTOTYPE", EPROTOTYPE);
+ initConstant(env, c, "ERANGE", ERANGE);
+ initConstant(env, c, "EROFS", EROFS);
+ initConstant(env, c, "ESPIPE", ESPIPE);
+ initConstant(env, c, "ESRCH", ESRCH);
+ initConstant(env, c, "ESTALE", ESTALE);
+ initConstant(env, c, "ETH_P_ALL", ETH_P_ALL);
+ initConstant(env, c, "ETH_P_ARP", ETH_P_ARP);
+ initConstant(env, c, "ETH_P_IP", ETH_P_IP);
+ initConstant(env, c, "ETH_P_IPV6", ETH_P_IPV6);
+ initConstant(env, c, "ETIME", ETIME);
+ initConstant(env, c, "ETIMEDOUT", ETIMEDOUT);
+ initConstant(env, c, "ETXTBSY", ETXTBSY);
+ initConstant(env, c, "EUSERS", EUSERS);
+#if EWOULDBLOCK != EAGAIN
+#error EWOULDBLOCK != EAGAIN
+#endif
+ initConstant(env, c, "EXDEV", EXDEV);
+ initConstant(env, c, "EXIT_FAILURE", EXIT_FAILURE);
+ initConstant(env, c, "EXIT_SUCCESS", EXIT_SUCCESS);
+ initConstant(env, c, "FD_CLOEXEC", FD_CLOEXEC);
+ initConstant(env, c, "FIONREAD", FIONREAD);
+ initConstant(env, c, "F_DUPFD", F_DUPFD);
+ initConstant(env, c, "F_DUPFD_CLOEXEC", F_DUPFD_CLOEXEC);
+ initConstant(env, c, "F_GETFD", F_GETFD);
+ initConstant(env, c, "F_GETFL", F_GETFL);
+ initConstant(env, c, "F_GETLK", F_GETLK);
+#if defined(F_GETLK64)
+ initConstant(env, c, "F_GETLK64", F_GETLK64);
+#endif
+ initConstant(env, c, "F_GETOWN", F_GETOWN);
+ initConstant(env, c, "F_OK", F_OK);
+ initConstant(env, c, "F_RDLCK", F_RDLCK);
+ initConstant(env, c, "F_SETFD", F_SETFD);
+ initConstant(env, c, "F_SETFL", F_SETFL);
+ initConstant(env, c, "F_SETLK", F_SETLK);
+#if defined(F_SETLK64)
+ initConstant(env, c, "F_SETLK64", F_SETLK64);
+#endif
+ initConstant(env, c, "F_SETLKW", F_SETLKW);
+#if defined(F_SETLKW64)
+ initConstant(env, c, "F_SETLKW64", F_SETLKW64);
+#endif
+ initConstant(env, c, "F_SETOWN", F_SETOWN);
+ initConstant(env, c, "F_UNLCK", F_UNLCK);
+ initConstant(env, c, "F_WRLCK", F_WRLCK);
+ initConstant(env, c, "ICMP_ECHO", ICMP_ECHO);
+ initConstant(env, c, "ICMP_ECHOREPLY", ICMP_ECHOREPLY);
+ initConstant(env, c, "ICMP6_ECHO_REQUEST", ICMP6_ECHO_REQUEST);
+ initConstant(env, c, "ICMP6_ECHO_REPLY", ICMP6_ECHO_REPLY);
+#if defined(IFA_F_DADFAILED)
+ initConstant(env, c, "IFA_F_DADFAILED", IFA_F_DADFAILED);
+#endif
+#if defined(IFA_F_DEPRECATED)
+ initConstant(env, c, "IFA_F_DEPRECATED", IFA_F_DEPRECATED);
+#endif
+#if defined(IFA_F_HOMEADDRESS)
+ initConstant(env, c, "IFA_F_HOMEADDRESS", IFA_F_HOMEADDRESS);
+#endif
+#if defined(IFA_F_MANAGETEMPADDR)
+ initConstant(env, c, "IFA_F_MANAGETEMPADDR", IFA_F_MANAGETEMPADDR);
+#endif
+#if defined(IFA_F_NODAD)
+ initConstant(env, c, "IFA_F_NODAD", IFA_F_NODAD);
+#endif
+#if defined(IFA_F_NOPREFIXROUTE)
+ initConstant(env, c, "IFA_F_NOPREFIXROUTE", IFA_F_NOPREFIXROUTE);
+#endif
+#if defined(IFA_F_OPTIMISTIC)
+ initConstant(env, c, "IFA_F_OPTIMISTIC", IFA_F_OPTIMISTIC);
+#endif
+#if defined(IFA_F_PERMANENT)
+ initConstant(env, c, "IFA_F_PERMANENT", IFA_F_PERMANENT);
+#endif
+#if defined(IFA_F_SECONDARY)
+ initConstant(env, c, "IFA_F_SECONDARY", IFA_F_SECONDARY);
+#endif
+#if defined(IFA_F_TEMPORARY)
+ initConstant(env, c, "IFA_F_TEMPORARY", IFA_F_TEMPORARY);
+#endif
+#if defined(IFA_F_TENTATIVE)
+ initConstant(env, c, "IFA_F_TENTATIVE", IFA_F_TENTATIVE);
+#endif
+ initConstant(env, c, "IFF_ALLMULTI", IFF_ALLMULTI);
+#if defined(IFF_AUTOMEDIA)
+ initConstant(env, c, "IFF_AUTOMEDIA", IFF_AUTOMEDIA);
+#endif
+ initConstant(env, c, "IFF_BROADCAST", IFF_BROADCAST);
+ initConstant(env, c, "IFF_DEBUG", IFF_DEBUG);
+#if defined(IFF_DYNAMIC)
+ initConstant(env, c, "IFF_DYNAMIC", IFF_DYNAMIC);
+#endif
+ initConstant(env, c, "IFF_LOOPBACK", IFF_LOOPBACK);
+#if defined(IFF_MASTER)
+ initConstant(env, c, "IFF_MASTER", IFF_MASTER);
+#endif
+ initConstant(env, c, "IFF_MULTICAST", IFF_MULTICAST);
+ initConstant(env, c, "IFF_NOARP", IFF_NOARP);
+ initConstant(env, c, "IFF_NOTRAILERS", IFF_NOTRAILERS);
+ initConstant(env, c, "IFF_POINTOPOINT", IFF_POINTOPOINT);
+#if defined(IFF_PORTSEL)
+ initConstant(env, c, "IFF_PORTSEL", IFF_PORTSEL);
+#endif
+ initConstant(env, c, "IFF_PROMISC", IFF_PROMISC);
+ initConstant(env, c, "IFF_RUNNING", IFF_RUNNING);
+#if defined(IFF_SLAVE)
+ initConstant(env, c, "IFF_SLAVE", IFF_SLAVE);
+#endif
+ initConstant(env, c, "IFF_UP", IFF_UP);
+ initConstant(env, c, "IPPROTO_ICMP", IPPROTO_ICMP);
+ initConstant(env, c, "IPPROTO_ICMPV6", IPPROTO_ICMPV6);
+ initConstant(env, c, "IPPROTO_IP", IPPROTO_IP);
+ initConstant(env, c, "IPPROTO_IPV6", IPPROTO_IPV6);
+ initConstant(env, c, "IPPROTO_RAW", IPPROTO_RAW);
+ initConstant(env, c, "IPPROTO_TCP", IPPROTO_TCP);
+ initConstant(env, c, "IPPROTO_UDP", IPPROTO_UDP);
+ initConstant(env, c, "IPPROTO_ESP", IPPROTO_ESP);
+ initConstant(env, c, "IPV6_CHECKSUM", IPV6_CHECKSUM);
+ initConstant(env, c, "IPV6_MULTICAST_HOPS", IPV6_MULTICAST_HOPS);
+ initConstant(env, c, "IPV6_MULTICAST_IF", IPV6_MULTICAST_IF);
+ initConstant(env, c, "IPV6_MULTICAST_LOOP", IPV6_MULTICAST_LOOP);
+#if defined(IPV6_PKTINFO)
+ initConstant(env, c, "IPV6_PKTINFO", IPV6_PKTINFO);
+#endif
+#if defined(IPV6_RECVDSTOPTS)
+ initConstant(env, c, "IPV6_RECVDSTOPTS", IPV6_RECVDSTOPTS);
+#endif
+#if defined(IPV6_RECVHOPLIMIT)
+ initConstant(env, c, "IPV6_RECVHOPLIMIT", IPV6_RECVHOPLIMIT);
+#endif
+#if defined(IPV6_RECVHOPOPTS)
+ initConstant(env, c, "IPV6_RECVHOPOPTS", IPV6_RECVHOPOPTS);
+#endif
+#if defined(IPV6_RECVPKTINFO)
+ initConstant(env, c, "IPV6_RECVPKTINFO", IPV6_RECVPKTINFO);
+#endif
+#if defined(IPV6_RECVRTHDR)
+ initConstant(env, c, "IPV6_RECVRTHDR", IPV6_RECVRTHDR);
+#endif
+#if defined(IPV6_RECVTCLASS)
+ initConstant(env, c, "IPV6_RECVTCLASS", IPV6_RECVTCLASS);
+#endif
+#if defined(IPV6_TCLASS)
+ initConstant(env, c, "IPV6_TCLASS", IPV6_TCLASS);
+#endif
+ initConstant(env, c, "IPV6_UNICAST_HOPS", IPV6_UNICAST_HOPS);
+ initConstant(env, c, "IPV6_V6ONLY", IPV6_V6ONLY);
+ initConstant(env, c, "IP_MULTICAST_ALL", IP_MULTICAST_ALL);
+ initConstant(env, c, "IP_MULTICAST_IF", IP_MULTICAST_IF);
+ initConstant(env, c, "IP_MULTICAST_LOOP", IP_MULTICAST_LOOP);
+ initConstant(env, c, "IP_MULTICAST_TTL", IP_MULTICAST_TTL);
+ initConstant(env, c, "IP_RECVTOS", IP_RECVTOS);
+ initConstant(env, c, "IP_TOS", IP_TOS);
+ initConstant(env, c, "IP_TTL", IP_TTL);
+#if defined(_LINUX_CAPABILITY_VERSION_3)
+ initConstant(env, c, "_LINUX_CAPABILITY_VERSION_3", _LINUX_CAPABILITY_VERSION_3);
+#endif
+ initConstant(env, c, "MAP_FIXED", MAP_FIXED);
+ initConstant(env, c, "MAP_ANONYMOUS", MAP_ANONYMOUS);
+ initConstant(env, c, "MAP_POPULATE", MAP_POPULATE);
+ initConstant(env, c, "MAP_PRIVATE", MAP_PRIVATE);
+ initConstant(env, c, "MAP_SHARED", MAP_SHARED);
+#if defined(MCAST_JOIN_GROUP)
+ initConstant(env, c, "MCAST_JOIN_GROUP", MCAST_JOIN_GROUP);
+#endif
+#if defined(MCAST_LEAVE_GROUP)
+ initConstant(env, c, "MCAST_LEAVE_GROUP", MCAST_LEAVE_GROUP);
+#endif
+#if defined(MCAST_JOIN_SOURCE_GROUP)
+ initConstant(env, c, "MCAST_JOIN_SOURCE_GROUP", MCAST_JOIN_SOURCE_GROUP);
+#endif
+#if defined(MCAST_LEAVE_SOURCE_GROUP)
+ initConstant(env, c, "MCAST_LEAVE_SOURCE_GROUP", MCAST_LEAVE_SOURCE_GROUP);
+#endif
+#if defined(MCAST_BLOCK_SOURCE)
+ initConstant(env, c, "MCAST_BLOCK_SOURCE", MCAST_BLOCK_SOURCE);
+#endif
+#if defined(MCAST_UNBLOCK_SOURCE)
+ initConstant(env, c, "MCAST_UNBLOCK_SOURCE", MCAST_UNBLOCK_SOURCE);
+#endif
+ initConstant(env, c, "MCL_CURRENT", MCL_CURRENT);
+ initConstant(env, c, "MCL_FUTURE", MCL_FUTURE);
+#if defined(MFD_CLOEXEC)
+ initConstant(env, c, "MFD_CLOEXEC", MFD_CLOEXEC);
+#endif
+ initConstant(env, c, "MSG_CTRUNC", MSG_CTRUNC);
+ initConstant(env, c, "MSG_DONTROUTE", MSG_DONTROUTE);
+ initConstant(env, c, "MSG_EOR", MSG_EOR);
+ initConstant(env, c, "MSG_OOB", MSG_OOB);
+ initConstant(env, c, "MSG_PEEK", MSG_PEEK);
+ initConstant(env, c, "MSG_TRUNC", MSG_TRUNC);
+ initConstant(env, c, "MSG_WAITALL", MSG_WAITALL);
+ initConstant(env, c, "MS_ASYNC", MS_ASYNC);
+ initConstant(env, c, "MS_INVALIDATE", MS_INVALIDATE);
+ initConstant(env, c, "MS_SYNC", MS_SYNC);
+ initConstant(env, c, "NETLINK_NETFILTER", NETLINK_NETFILTER);
+ initConstant(env, c, "NETLINK_ROUTE", NETLINK_ROUTE);
+ initConstant(env, c, "NETLINK_INET_DIAG", NETLINK_INET_DIAG);
+ initConstant(env, c, "NETLINK_XFRM", NETLINK_XFRM);
+ initConstant(env, c, "NI_DGRAM", NI_DGRAM);
+ initConstant(env, c, "NI_NAMEREQD", NI_NAMEREQD);
+ initConstant(env, c, "NI_NOFQDN", NI_NOFQDN);
+ initConstant(env, c, "NI_NUMERICHOST", NI_NUMERICHOST);
+ initConstant(env, c, "NI_NUMERICSERV", NI_NUMERICSERV);
+ initConstant(env, c, "O_ACCMODE", O_ACCMODE);
+ initConstant(env, c, "O_APPEND", O_APPEND);
+ initConstant(env, c, "O_CLOEXEC", O_CLOEXEC);
+ initConstant(env, c, "O_CREAT", O_CREAT);
+ initConstant(env, c, "O_DIRECT", O_DIRECT);
+ initConstant(env, c, "O_EXCL", O_EXCL);
+ initConstant(env, c, "O_NOCTTY", O_NOCTTY);
+ initConstant(env, c, "O_NOFOLLOW", O_NOFOLLOW);
+ initConstant(env, c, "O_NONBLOCK", O_NONBLOCK);
+ initConstant(env, c, "O_RDONLY", O_RDONLY);
+ initConstant(env, c, "O_RDWR", O_RDWR);
+ initConstant(env, c, "O_SYNC", O_SYNC);
+ initConstant(env, c, "O_DSYNC", O_DSYNC);
+ initConstant(env, c, "O_TRUNC", O_TRUNC);
+ initConstant(env, c, "O_WRONLY", O_WRONLY);
+ initConstant(env, c, "POLLERR", POLLERR);
+ initConstant(env, c, "POLLHUP", POLLHUP);
+ initConstant(env, c, "POLLIN", POLLIN);
+ initConstant(env, c, "POLLNVAL", POLLNVAL);
+ initConstant(env, c, "POLLOUT", POLLOUT);
+ initConstant(env, c, "POLLPRI", POLLPRI);
+ initConstant(env, c, "POLLRDBAND", POLLRDBAND);
+ initConstant(env, c, "POLLRDNORM", POLLRDNORM);
+ initConstant(env, c, "POLLWRBAND", POLLWRBAND);
+ initConstant(env, c, "POLLWRNORM", POLLWRNORM);
+#if defined(PR_CAP_AMBIENT)
+ initConstant(env, c, "PR_CAP_AMBIENT", PR_CAP_AMBIENT);
+#endif
+#if defined(PR_CAP_AMBIENT_RAISE)
+ initConstant(env, c, "PR_CAP_AMBIENT_RAISE", PR_CAP_AMBIENT_RAISE);
+#endif
+#if defined(PR_GET_DUMPABLE)
+ initConstant(env, c, "PR_GET_DUMPABLE", PR_GET_DUMPABLE);
+#endif
+#if defined(PR_SET_DUMPABLE)
+ initConstant(env, c, "PR_SET_DUMPABLE", PR_SET_DUMPABLE);
+#endif
+#if defined(PR_SET_NO_NEW_PRIVS)
+ initConstant(env, c, "PR_SET_NO_NEW_PRIVS", PR_SET_NO_NEW_PRIVS);
+#endif
+ initConstant(env, c, "PROT_EXEC", PROT_EXEC);
+ initConstant(env, c, "PROT_NONE", PROT_NONE);
+ initConstant(env, c, "PROT_READ", PROT_READ);
+ initConstant(env, c, "PROT_WRITE", PROT_WRITE);
+ initConstant(env, c, "R_OK", R_OK);
+ initConstant(env, c, "RLIMIT_NOFILE", RLIMIT_NOFILE);
+// NOTE: The RT_* constants are not preprocessor defines, they're enum
+// members. The best we can do (barring UAPI / kernel version checks) is
+// to hope they exist on all host linuxes we're building on. These
+// constants have been around since 2.6.35 at least, so we should be ok.
+ initConstant(env, c, "RT_SCOPE_HOST", RT_SCOPE_HOST);
+ initConstant(env, c, "RT_SCOPE_LINK", RT_SCOPE_LINK);
+ initConstant(env, c, "RT_SCOPE_NOWHERE", RT_SCOPE_NOWHERE);
+ initConstant(env, c, "RT_SCOPE_SITE", RT_SCOPE_SITE);
+ initConstant(env, c, "RT_SCOPE_UNIVERSE", RT_SCOPE_UNIVERSE);
+ initConstant(env, c, "RTMGRP_IPV4_IFADDR", RTMGRP_IPV4_IFADDR);
+ initConstant(env, c, "RTMGRP_IPV4_MROUTE", RTMGRP_IPV4_MROUTE);
+ initConstant(env, c, "RTMGRP_IPV4_ROUTE", RTMGRP_IPV4_ROUTE);
+ initConstant(env, c, "RTMGRP_IPV4_RULE", RTMGRP_IPV4_RULE);
+ initConstant(env, c, "RTMGRP_IPV6_IFADDR", RTMGRP_IPV6_IFADDR);
+ initConstant(env, c, "RTMGRP_IPV6_IFINFO", RTMGRP_IPV6_IFINFO);
+ initConstant(env, c, "RTMGRP_IPV6_MROUTE", RTMGRP_IPV6_MROUTE);
+ initConstant(env, c, "RTMGRP_IPV6_PREFIX", RTMGRP_IPV6_PREFIX);
+ initConstant(env, c, "RTMGRP_IPV6_ROUTE", RTMGRP_IPV6_ROUTE);
+ initConstant(env, c, "RTMGRP_LINK", RTMGRP_LINK);
+ initConstant(env, c, "RTMGRP_NEIGH", RTMGRP_NEIGH);
+ initConstant(env, c, "RTMGRP_NOTIFY", RTMGRP_NOTIFY);
+ initConstant(env, c, "RTMGRP_TC", RTMGRP_TC);
+ initConstant(env, c, "SEEK_CUR", SEEK_CUR);
+ initConstant(env, c, "SEEK_END", SEEK_END);
+ initConstant(env, c, "SEEK_SET", SEEK_SET);
+ initConstant(env, c, "SHUT_RD", SHUT_RD);
+ initConstant(env, c, "SHUT_RDWR", SHUT_RDWR);
+ initConstant(env, c, "SHUT_WR", SHUT_WR);
+ initConstant(env, c, "SIGABRT", SIGABRT);
+ initConstant(env, c, "SIGALRM", SIGALRM);
+ initConstant(env, c, "SIGBUS", SIGBUS);
+ initConstant(env, c, "SIGCHLD", SIGCHLD);
+ initConstant(env, c, "SIGCONT", SIGCONT);
+ initConstant(env, c, "SIGFPE", SIGFPE);
+ initConstant(env, c, "SIGHUP", SIGHUP);
+ initConstant(env, c, "SIGILL", SIGILL);
+ initConstant(env, c, "SIGINT", SIGINT);
+ initConstant(env, c, "SIGIO", SIGIO);
+ initConstant(env, c, "SIGKILL", SIGKILL);
+ initConstant(env, c, "SIGPIPE", SIGPIPE);
+ initConstant(env, c, "SIGPROF", SIGPROF);
+#if defined(SIGPWR)
+ initConstant(env, c, "SIGPWR", SIGPWR);
+#endif
+ initConstant(env, c, "SIGQUIT", SIGQUIT);
+#if defined(SIGRTMAX)
+ initConstant(env, c, "SIGRTMAX", SIGRTMAX);
+#endif
+#if defined(SIGRTMIN)
+ initConstant(env, c, "SIGRTMIN", SIGRTMIN);
+#endif
+ initConstant(env, c, "SIGSEGV", SIGSEGV);
+#if defined(SIGSTKFLT)
+ initConstant(env, c, "SIGSTKFLT", SIGSTKFLT);
+#endif
+ initConstant(env, c, "SIGSTOP", SIGSTOP);
+ initConstant(env, c, "SIGSYS", SIGSYS);
+ initConstant(env, c, "SIGTERM", SIGTERM);
+ initConstant(env, c, "SIGTRAP", SIGTRAP);
+ initConstant(env, c, "SIGTSTP", SIGTSTP);
+ initConstant(env, c, "SIGTTIN", SIGTTIN);
+ initConstant(env, c, "SIGTTOU", SIGTTOU);
+ initConstant(env, c, "SIGURG", SIGURG);
+ initConstant(env, c, "SIGUSR1", SIGUSR1);
+ initConstant(env, c, "SIGUSR2", SIGUSR2);
+ initConstant(env, c, "SIGVTALRM", SIGVTALRM);
+ initConstant(env, c, "SIGWINCH", SIGWINCH);
+ initConstant(env, c, "SIGXCPU", SIGXCPU);
+ initConstant(env, c, "SIGXFSZ", SIGXFSZ);
+ initConstant(env, c, "SIOCGIFADDR", SIOCGIFADDR);
+ initConstant(env, c, "SIOCGIFBRDADDR", SIOCGIFBRDADDR);
+ initConstant(env, c, "SIOCGIFDSTADDR", SIOCGIFDSTADDR);
+ initConstant(env, c, "SIOCGIFNETMASK", SIOCGIFNETMASK);
+ initConstant(env, c, "SOCK_CLOEXEC", SOCK_CLOEXEC);
+ initConstant(env, c, "SOCK_DGRAM", SOCK_DGRAM);
+ initConstant(env, c, "SOCK_NONBLOCK", SOCK_NONBLOCK);
+ initConstant(env, c, "SOCK_RAW", SOCK_RAW);
+ initConstant(env, c, "SOCK_SEQPACKET", SOCK_SEQPACKET);
+ initConstant(env, c, "SOCK_STREAM", SOCK_STREAM);
+ initConstant(env, c, "SOL_SOCKET", SOL_SOCKET);
+#if defined(SOL_UDP)
+ initConstant(env, c, "SOL_UDP", SOL_UDP);
+#endif
+ initConstant(env, c, "SOL_PACKET", SOL_PACKET);
+#if defined(SO_BINDTODEVICE)
+ initConstant(env, c, "SO_BINDTODEVICE", SO_BINDTODEVICE);
+#endif
+ initConstant(env, c, "SO_BROADCAST", SO_BROADCAST);
+ initConstant(env, c, "SO_DEBUG", SO_DEBUG);
+#if defined(SO_DOMAIN)
+ initConstant(env, c, "SO_DOMAIN", SO_DOMAIN);
+#endif
+ initConstant(env, c, "SO_DONTROUTE", SO_DONTROUTE);
+ initConstant(env, c, "SO_ERROR", SO_ERROR);
+ initConstant(env, c, "SO_KEEPALIVE", SO_KEEPALIVE);
+ initConstant(env, c, "SO_LINGER", SO_LINGER);
+ initConstant(env, c, "SO_OOBINLINE", SO_OOBINLINE);
+#if defined(SO_PASSCRED)
+ initConstant(env, c, "SO_PASSCRED", SO_PASSCRED);
+#endif
+#if defined(SO_PEERCRED)
+ initConstant(env, c, "SO_PEERCRED", SO_PEERCRED);
+#endif
+#if defined(SO_PROTOCOL)
+ initConstant(env, c, "SO_PROTOCOL", SO_PROTOCOL);
+#endif
+ initConstant(env, c, "SO_RCVBUF", SO_RCVBUF);
+ initConstant(env, c, "SO_RCVLOWAT", SO_RCVLOWAT);
+ initConstant(env, c, "SO_RCVTIMEO", SO_RCVTIMEO);
+ initConstant(env, c, "SO_REUSEADDR", SO_REUSEADDR);
+ initConstant(env, c, "SO_SNDBUF", SO_SNDBUF);
+ initConstant(env, c, "SO_SNDLOWAT", SO_SNDLOWAT);
+ initConstant(env, c, "SO_SNDTIMEO", SO_SNDTIMEO);
+ initConstant(env, c, "SO_TYPE", SO_TYPE);
+#if defined(PACKET_IGNORE_OUTGOING)
+ initConstant(env, c, "PACKET_IGNORE_OUTGOING", PACKET_IGNORE_OUTGOING);
+#endif
+ initConstant(env, c, "SPLICE_F_MOVE", SPLICE_F_MOVE);
+ initConstant(env, c, "SPLICE_F_NONBLOCK", SPLICE_F_NONBLOCK);
+ initConstant(env, c, "SPLICE_F_MORE", SPLICE_F_MORE);
+ initConstant(env, c, "STDERR_FILENO", STDERR_FILENO);
+ initConstant(env, c, "STDIN_FILENO", STDIN_FILENO);
+ initConstant(env, c, "STDOUT_FILENO", STDOUT_FILENO);
+ initConstant(env, c, "ST_MANDLOCK", ST_MANDLOCK);
+ initConstant(env, c, "ST_NOATIME", ST_NOATIME);
+ initConstant(env, c, "ST_NODEV", ST_NODEV);
+ initConstant(env, c, "ST_NODIRATIME", ST_NODIRATIME);
+ initConstant(env, c, "ST_NOEXEC", ST_NOEXEC);
+ initConstant(env, c, "ST_NOSUID", ST_NOSUID);
+ initConstant(env, c, "ST_RDONLY", ST_RDONLY);
+ initConstant(env, c, "ST_RELATIME", ST_RELATIME);
+ initConstant(env, c, "ST_SYNCHRONOUS", ST_SYNCHRONOUS);
+ initConstant(env, c, "S_IFBLK", S_IFBLK);
+ initConstant(env, c, "S_IFCHR", S_IFCHR);
+ initConstant(env, c, "S_IFDIR", S_IFDIR);
+ initConstant(env, c, "S_IFIFO", S_IFIFO);
+ initConstant(env, c, "S_IFLNK", S_IFLNK);
+ initConstant(env, c, "S_IFMT", S_IFMT);
+ initConstant(env, c, "S_IFREG", S_IFREG);
+ initConstant(env, c, "S_IFSOCK", S_IFSOCK);
+ initConstant(env, c, "S_IRGRP", S_IRGRP);
+ initConstant(env, c, "S_IROTH", S_IROTH);
+ initConstant(env, c, "S_IRUSR", S_IRUSR);
+ initConstant(env, c, "S_IRWXG", S_IRWXG);
+ initConstant(env, c, "S_IRWXO", S_IRWXO);
+ initConstant(env, c, "S_IRWXU", S_IRWXU);
+ initConstant(env, c, "S_ISGID", S_ISGID);
+ initConstant(env, c, "S_ISUID", S_ISUID);
+ initConstant(env, c, "S_ISVTX", S_ISVTX);
+ initConstant(env, c, "S_IWGRP", S_IWGRP);
+ initConstant(env, c, "S_IWOTH", S_IWOTH);
+ initConstant(env, c, "S_IWUSR", S_IWUSR);
+ initConstant(env, c, "S_IXGRP", S_IXGRP);
+ initConstant(env, c, "S_IXOTH", S_IXOTH);
+ initConstant(env, c, "S_IXUSR", S_IXUSR);
+ initConstant(env, c, "TCP_NODELAY", TCP_NODELAY);
+#if defined(TCP_USER_TIMEOUT)
+ initConstant(env, c, "TCP_USER_TIMEOUT", TCP_USER_TIMEOUT);
+#endif
+ initConstant(env, c, "TIOCOUTQ", TIOCOUTQ);
+ initConstant(env, c, "UDP_ENCAP", UDP_ENCAP);
+ initConstant(env, c, "UDP_ENCAP_ESPINUDP_NON_IKE", UDP_ENCAP_ESPINUDP_NON_IKE);
+ initConstant(env, c, "UDP_ENCAP_ESPINUDP", UDP_ENCAP_ESPINUDP);
+#if defined(UDP_GRO)
+ initConstant(env, c, "UDP_GRO", UDP_GRO);
+#endif
+#if defined(UDP_SEGMENT)
+ initConstant(env, c, "UDP_SEGMENT", UDP_SEGMENT);
+#endif
+ // UNIX_PATH_MAX is mentioned in some versions of unix(7), but not actually declared.
+ initConstant(env, c, "UNIX_PATH_MAX", sizeof(sockaddr_un::sun_path));
+ initConstant(env, c, "WCONTINUED", WCONTINUED);
+ initConstant(env, c, "WEXITED", WEXITED);
+ initConstant(env, c, "WNOHANG", WNOHANG);
+ initConstant(env, c, "WNOWAIT", WNOWAIT);
+ initConstant(env, c, "WSTOPPED", WSTOPPED);
+ initConstant(env, c, "WUNTRACED", WUNTRACED);
+ initConstant(env, c, "W_OK", W_OK);
+ initConstant(env, c, "XATTR_CREATE", XATTR_CREATE);
+ initConstant(env, c, "XATTR_REPLACE", XATTR_REPLACE);
+ initConstant(env, c, "X_OK", X_OK);
+ initConstant(env, c, "_SC_2_CHAR_TERM", _SC_2_CHAR_TERM);
+ initConstant(env, c, "_SC_2_C_BIND", _SC_2_C_BIND);
+ initConstant(env, c, "_SC_2_C_DEV", _SC_2_C_DEV);
+#if defined(_SC_2_C_VERSION)
+ initConstant(env, c, "_SC_2_C_VERSION", _SC_2_C_VERSION);
+#endif
+ initConstant(env, c, "_SC_2_FORT_DEV", _SC_2_FORT_DEV);
+ initConstant(env, c, "_SC_2_FORT_RUN", _SC_2_FORT_RUN);
+ initConstant(env, c, "_SC_2_LOCALEDEF", _SC_2_LOCALEDEF);
+ initConstant(env, c, "_SC_2_SW_DEV", _SC_2_SW_DEV);
+ initConstant(env, c, "_SC_2_UPE", _SC_2_UPE);
+ initConstant(env, c, "_SC_2_VERSION", _SC_2_VERSION);
+ initConstant(env, c, "_SC_AIO_LISTIO_MAX", _SC_AIO_LISTIO_MAX);
+ initConstant(env, c, "_SC_AIO_MAX", _SC_AIO_MAX);
+ initConstant(env, c, "_SC_AIO_PRIO_DELTA_MAX", _SC_AIO_PRIO_DELTA_MAX);
+ initConstant(env, c, "_SC_ARG_MAX", _SC_ARG_MAX);
+ initConstant(env, c, "_SC_ASYNCHRONOUS_IO", _SC_ASYNCHRONOUS_IO);
+ initConstant(env, c, "_SC_ATEXIT_MAX", _SC_ATEXIT_MAX);
+#if defined(_SC_AVPHYS_PAGES)
+ initConstant(env, c, "_SC_AVPHYS_PAGES", _SC_AVPHYS_PAGES);
+#endif
+ initConstant(env, c, "_SC_BC_BASE_MAX", _SC_BC_BASE_MAX);
+ initConstant(env, c, "_SC_BC_DIM_MAX", _SC_BC_DIM_MAX);
+ initConstant(env, c, "_SC_BC_SCALE_MAX", _SC_BC_SCALE_MAX);
+ initConstant(env, c, "_SC_BC_STRING_MAX", _SC_BC_STRING_MAX);
+ initConstant(env, c, "_SC_CHILD_MAX", _SC_CHILD_MAX);
+ initConstant(env, c, "_SC_CLK_TCK", _SC_CLK_TCK);
+ initConstant(env, c, "_SC_COLL_WEIGHTS_MAX", _SC_COLL_WEIGHTS_MAX);
+ initConstant(env, c, "_SC_DELAYTIMER_MAX", _SC_DELAYTIMER_MAX);
+ initConstant(env, c, "_SC_EXPR_NEST_MAX", _SC_EXPR_NEST_MAX);
+ initConstant(env, c, "_SC_FSYNC", _SC_FSYNC);
+ initConstant(env, c, "_SC_GETGR_R_SIZE_MAX", _SC_GETGR_R_SIZE_MAX);
+ initConstant(env, c, "_SC_GETPW_R_SIZE_MAX", _SC_GETPW_R_SIZE_MAX);
+ initConstant(env, c, "_SC_IOV_MAX", _SC_IOV_MAX);
+ initConstant(env, c, "_SC_JOB_CONTROL", _SC_JOB_CONTROL);
+ initConstant(env, c, "_SC_LINE_MAX", _SC_LINE_MAX);
+ initConstant(env, c, "_SC_LOGIN_NAME_MAX", _SC_LOGIN_NAME_MAX);
+ initConstant(env, c, "_SC_MAPPED_FILES", _SC_MAPPED_FILES);
+ initConstant(env, c, "_SC_MEMLOCK", _SC_MEMLOCK);
+ initConstant(env, c, "_SC_MEMLOCK_RANGE", _SC_MEMLOCK_RANGE);
+ initConstant(env, c, "_SC_MEMORY_PROTECTION", _SC_MEMORY_PROTECTION);
+ initConstant(env, c, "_SC_MESSAGE_PASSING", _SC_MESSAGE_PASSING);
+ initConstant(env, c, "_SC_MQ_OPEN_MAX", _SC_MQ_OPEN_MAX);
+ initConstant(env, c, "_SC_MQ_PRIO_MAX", _SC_MQ_PRIO_MAX);
+ initConstant(env, c, "_SC_NGROUPS_MAX", _SC_NGROUPS_MAX);
+ initConstant(env, c, "_SC_NPROCESSORS_CONF", _SC_NPROCESSORS_CONF);
+ initConstant(env, c, "_SC_NPROCESSORS_ONLN", _SC_NPROCESSORS_ONLN);
+ initConstant(env, c, "_SC_OPEN_MAX", _SC_OPEN_MAX);
+ initConstant(env, c, "_SC_PAGESIZE", _SC_PAGESIZE);
+ initConstant(env, c, "_SC_PAGE_SIZE", _SC_PAGE_SIZE);
+ initConstant(env, c, "_SC_PASS_MAX", _SC_PASS_MAX);
+#if defined(_SC_PHYS_PAGES)
+ initConstant(env, c, "_SC_PHYS_PAGES", _SC_PHYS_PAGES);
+#endif
+ initConstant(env, c, "_SC_PRIORITIZED_IO", _SC_PRIORITIZED_IO);
+ initConstant(env, c, "_SC_PRIORITY_SCHEDULING", _SC_PRIORITY_SCHEDULING);
+ initConstant(env, c, "_SC_REALTIME_SIGNALS", _SC_REALTIME_SIGNALS);
+ initConstant(env, c, "_SC_RE_DUP_MAX", _SC_RE_DUP_MAX);
+ initConstant(env, c, "_SC_RTSIG_MAX", _SC_RTSIG_MAX);
+ initConstant(env, c, "_SC_SAVED_IDS", _SC_SAVED_IDS);
+ initConstant(env, c, "_SC_SEMAPHORES", _SC_SEMAPHORES);
+ initConstant(env, c, "_SC_SEM_NSEMS_MAX", _SC_SEM_NSEMS_MAX);
+ initConstant(env, c, "_SC_SEM_VALUE_MAX", _SC_SEM_VALUE_MAX);
+ initConstant(env, c, "_SC_SHARED_MEMORY_OBJECTS", _SC_SHARED_MEMORY_OBJECTS);
+ initConstant(env, c, "_SC_SIGQUEUE_MAX", _SC_SIGQUEUE_MAX);
+ initConstant(env, c, "_SC_STREAM_MAX", _SC_STREAM_MAX);
+ initConstant(env, c, "_SC_SYNCHRONIZED_IO", _SC_SYNCHRONIZED_IO);
+ initConstant(env, c, "_SC_THREADS", _SC_THREADS);
+ initConstant(env, c, "_SC_THREAD_ATTR_STACKADDR", _SC_THREAD_ATTR_STACKADDR);
+ initConstant(env, c, "_SC_THREAD_ATTR_STACKSIZE", _SC_THREAD_ATTR_STACKSIZE);
+ initConstant(env, c, "_SC_THREAD_DESTRUCTOR_ITERATIONS", _SC_THREAD_DESTRUCTOR_ITERATIONS);
+ initConstant(env, c, "_SC_THREAD_KEYS_MAX", _SC_THREAD_KEYS_MAX);
+ initConstant(env, c, "_SC_THREAD_PRIORITY_SCHEDULING", _SC_THREAD_PRIORITY_SCHEDULING);
+ initConstant(env, c, "_SC_THREAD_PRIO_INHERIT", _SC_THREAD_PRIO_INHERIT);
+ initConstant(env, c, "_SC_THREAD_PRIO_PROTECT", _SC_THREAD_PRIO_PROTECT);
+ initConstant(env, c, "_SC_THREAD_SAFE_FUNCTIONS", _SC_THREAD_SAFE_FUNCTIONS);
+ initConstant(env, c, "_SC_THREAD_STACK_MIN", _SC_THREAD_STACK_MIN);
+ initConstant(env, c, "_SC_THREAD_THREADS_MAX", _SC_THREAD_THREADS_MAX);
+ initConstant(env, c, "_SC_TIMERS", _SC_TIMERS);
+ initConstant(env, c, "_SC_TIMER_MAX", _SC_TIMER_MAX);
+ initConstant(env, c, "_SC_TTY_NAME_MAX", _SC_TTY_NAME_MAX);
+ initConstant(env, c, "_SC_TZNAME_MAX", _SC_TZNAME_MAX);
+ initConstant(env, c, "_SC_VERSION", _SC_VERSION);
+ initConstant(env, c, "_SC_XBS5_ILP32_OFF32", _SC_XBS5_ILP32_OFF32);
+ initConstant(env, c, "_SC_XBS5_ILP32_OFFBIG", _SC_XBS5_ILP32_OFFBIG);
+ initConstant(env, c, "_SC_XBS5_LP64_OFF64", _SC_XBS5_LP64_OFF64);
+ initConstant(env, c, "_SC_XBS5_LPBIG_OFFBIG", _SC_XBS5_LPBIG_OFFBIG);
+ initConstant(env, c, "_SC_XOPEN_CRYPT", _SC_XOPEN_CRYPT);
+ initConstant(env, c, "_SC_XOPEN_ENH_I18N", _SC_XOPEN_ENH_I18N);
+ initConstant(env, c, "_SC_XOPEN_LEGACY", _SC_XOPEN_LEGACY);
+ initConstant(env, c, "_SC_XOPEN_REALTIME", _SC_XOPEN_REALTIME);
+ initConstant(env, c, "_SC_XOPEN_REALTIME_THREADS", _SC_XOPEN_REALTIME_THREADS);
+ initConstant(env, c, "_SC_XOPEN_SHM", _SC_XOPEN_SHM);
+ initConstant(env, c, "_SC_XOPEN_UNIX", _SC_XOPEN_UNIX);
+ initConstant(env, c, "_SC_XOPEN_VERSION", _SC_XOPEN_VERSION);
+ initConstant(env, c, "_SC_XOPEN_XCU_VERSION", _SC_XOPEN_XCU_VERSION);
+}
+
+static JNINativeMethod gMethods[] = {
+ NATIVE_METHOD(OsConstants, initConstants, "()V"),
+};
+
+void register_android_system_OsConstants(JNIEnv* env) {
+ // [ravenwood-change] -- method moved to the nested class
+ jniRegisterNativeMethods(env, "android/system/OsConstants$Native", gMethods, NELEM(gMethods));
+}
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
new file mode 100644
index 0000000..34cf9f9
--- /dev/null
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <unistd.h>
+#include <nativehelper/JNIHelp.h>
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+// Defined in ravenwood_os_constants.cpp
+void register_android_system_OsConstants(JNIEnv* env);
+
+// ---- Exception related ----
+
+static void throwErrnoException(JNIEnv* env, const char* functionName) {
+ int error = errno;
+ jniThrowErrnoException(env, functionName, error);
+}
+
+template <typename rc_t>
+static rc_t throwIfMinusOne(JNIEnv* env, const char* name, rc_t rc) {
+ if (rc == rc_t(-1)) {
+ throwErrnoException(env, name);
+ }
+ return rc;
+}
+
+// ---- JNI methods ----
+
+typedef void (*FreeFunction)(void*);
+
+static void nApplyFreeFunction(JNIEnv*, jclass, jlong freeFunction, jlong ptr) {
+ void* nativePtr = reinterpret_cast<void*>(static_cast<uintptr_t>(ptr));
+ FreeFunction nativeFreeFunction
+ = reinterpret_cast<FreeFunction>(static_cast<uintptr_t>(freeFunction));
+ nativeFreeFunction(nativePtr);
+}
+
+static jint nFcntlInt(JNIEnv* env, jclass, jint fd, jint cmd, jint arg) {
+ return throwIfMinusOne(env, "fcntl", TEMP_FAILURE_RETRY(fcntl(fd, cmd, arg)));
+}
+
+static jlong nLseek(JNIEnv* env, jclass, jint fd, jlong offset, jint whence) {
+ return throwIfMinusOne(env, "lseek", TEMP_FAILURE_RETRY(lseek(fd, offset, whence)));
+}
+
+static jintArray nPipe2(JNIEnv* env, jclass, jint flags) {
+ int fds[2];
+ throwIfMinusOne(env, "pipe2", TEMP_FAILURE_RETRY(pipe2(fds, flags)));
+
+ jintArray result;
+ result = env->NewIntArray(2);
+ if (result == NULL) {
+ return NULL; /* out of memory error thrown */
+ }
+ env->SetIntArrayRegion(result, 0, 2, fds);
+ return result;
+}
+
+static jlong nDup(JNIEnv* env, jclass, jint fd) {
+ return throwIfMinusOne(env, "fcntl", TEMP_FAILURE_RETRY(fcntl(fd, F_DUPFD_CLOEXEC, 0)));
+}
+
+// ---- Registration ----
+
+static const JNINativeMethod sMethods[] =
+{
+ { "applyFreeFunction", "(JJ)V", (void*)nApplyFreeFunction },
+ { "nFcntlInt", "(III)I", (void*)nFcntlInt },
+ { "nLseek", "(IJI)J", (void*)nLseek },
+ { "nPipe2", "(I)[I", (void*)nPipe2 },
+ { "nDup", "(I)I", (void*)nDup },
+};
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
+{
+ JNIEnv* env = NULL;
+ jint result = -1;
+
+ if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+ ALOGE("GetEnv failed!");
+ return result;
+ }
+ ALOG_ASSERT(env, "Could not retrieve the env!");
+
+ ALOGI("%s: JNI_OnLoad", __FILE__);
+
+ jint res = jniRegisterNativeMethods(env, "com/android/ravenwood/common/RavenwoodRuntimeNative",
+ sMethods, NELEM(sMethods));
+ if (res < 0) {
+ return res;
+ }
+
+ register_android_system_OsConstants(env);
+
+ return JNI_VERSION_1_4;
+}
diff --git a/ravenwood/runtime-test/Android.bp b/ravenwood/runtime-test/Android.bp
new file mode 100644
index 0000000..4102920
--- /dev/null
+++ b/ravenwood/runtime-test/Android.bp
@@ -0,0 +1,23 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_ravenwood_test {
+ name: "RavenwoodRuntimeTest",
+
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ ],
+ srcs: [
+ "test/**/*.java",
+ ],
+ // sdk_version: "module_current",
+ auto_gen_config: true,
+}
diff --git a/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsConstantsTest.java b/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsConstantsTest.java
new file mode 100644
index 0000000..3332e24
--- /dev/null
+++ b/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsConstantsTest.java
@@ -0,0 +1,444 @@
+/*
+ * 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.ravenwood.runtimetest;
+
+// Copied from libcore/luni/src/test/java/libcore/android/system/OsConstantsTest.java
+
+import static android.system.OsConstants.CAP_TO_INDEX;
+import static android.system.OsConstants.CAP_TO_MASK;
+import static android.system.OsConstants.S_ISBLK;
+import static android.system.OsConstants.S_ISCHR;
+import static android.system.OsConstants.S_ISDIR;
+import static android.system.OsConstants.S_ISFIFO;
+import static android.system.OsConstants.S_ISLNK;
+import static android.system.OsConstants.S_ISREG;
+import static android.system.OsConstants.S_ISSOCK;
+import static android.system.OsConstants.WCOREDUMP;
+import static android.system.OsConstants.WEXITSTATUS;
+import static android.system.OsConstants.WIFEXITED;
+import static android.system.OsConstants.WIFSIGNALED;
+import static android.system.OsConstants.WIFSTOPPED;
+import static android.system.OsConstants.WSTOPSIG;
+import static android.system.OsConstants.WTERMSIG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.system.OsConstants;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class OsConstantsTest {
+
+ // http://b/15602893
+ @Test
+ public void testBug15602893() {
+ assertTrue(OsConstants.RT_SCOPE_HOST > 0);
+ assertTrue(OsConstants.RT_SCOPE_LINK > 0);
+ assertTrue(OsConstants.RT_SCOPE_SITE > 0);
+
+ assertTrue(OsConstants.IFA_F_TENTATIVE > 0);
+ }
+
+ // introduced for http://b/30402085
+ @Test
+ public void testTcpUserTimeoutIsDefined() {
+ assertTrue(OsConstants.TCP_USER_TIMEOUT > 0);
+ }
+
+ /**
+ * Verifies equality assertions given in the documentation for
+ * {@link OsConstants#SOCK_CLOEXEC} and {@link OsConstants#SOCK_NONBLOCK}.
+ */
+ @Test
+ public void testConstantsEqual() {
+ assertEquals(OsConstants.O_CLOEXEC, OsConstants.SOCK_CLOEXEC);
+ assertEquals(OsConstants.O_NONBLOCK, OsConstants.SOCK_NONBLOCK);
+ }
+
+ @Test
+ public void test_CAP_constants() {
+ assertEquals(0, OsConstants.CAP_CHOWN);
+ assertEquals(1, OsConstants.CAP_DAC_OVERRIDE);
+ assertEquals(2, OsConstants.CAP_DAC_READ_SEARCH);
+ assertEquals(3, OsConstants.CAP_FOWNER);
+ assertEquals(4, OsConstants.CAP_FSETID);
+ assertEquals(5, OsConstants.CAP_KILL);
+ assertEquals(6, OsConstants.CAP_SETGID);
+ assertEquals(7, OsConstants.CAP_SETUID);
+ assertEquals(8, OsConstants.CAP_SETPCAP);
+ assertEquals(9, OsConstants.CAP_LINUX_IMMUTABLE);
+ assertEquals(10, OsConstants.CAP_NET_BIND_SERVICE);
+ assertEquals(11, OsConstants.CAP_NET_BROADCAST);
+ assertEquals(12, OsConstants.CAP_NET_ADMIN);
+ assertEquals(13, OsConstants.CAP_NET_RAW);
+ assertEquals(14, OsConstants.CAP_IPC_LOCK);
+ assertEquals(15, OsConstants.CAP_IPC_OWNER);
+ assertEquals(16, OsConstants.CAP_SYS_MODULE);
+ assertEquals(17, OsConstants.CAP_SYS_RAWIO);
+ assertEquals(18, OsConstants.CAP_SYS_CHROOT);
+ assertEquals(19, OsConstants.CAP_SYS_PTRACE);
+ assertEquals(20, OsConstants.CAP_SYS_PACCT);
+ assertEquals(21, OsConstants.CAP_SYS_ADMIN);
+ assertEquals(22, OsConstants.CAP_SYS_BOOT);
+ assertEquals(23, OsConstants.CAP_SYS_NICE);
+ assertEquals(24, OsConstants.CAP_SYS_RESOURCE);
+ assertEquals(25, OsConstants.CAP_SYS_TIME);
+ assertEquals(26, OsConstants.CAP_SYS_TTY_CONFIG);
+ assertEquals(27, OsConstants.CAP_MKNOD);
+ assertEquals(28, OsConstants.CAP_LEASE);
+ assertEquals(29, OsConstants.CAP_AUDIT_WRITE);
+ assertEquals(30, OsConstants.CAP_AUDIT_CONTROL);
+ assertEquals(31, OsConstants.CAP_SETFCAP);
+ assertEquals(32, OsConstants.CAP_MAC_OVERRIDE);
+ assertEquals(33, OsConstants.CAP_MAC_ADMIN);
+ assertEquals(34, OsConstants.CAP_SYSLOG);
+ assertEquals(35, OsConstants.CAP_WAKE_ALARM);
+ assertEquals(36, OsConstants.CAP_BLOCK_SUSPEND);
+ // last constant
+ assertEquals(40, OsConstants.CAP_LAST_CAP);
+ }
+
+ @Test
+ public void test_CAP_TO_INDEX() {
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_CHOWN));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_DAC_OVERRIDE));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_DAC_READ_SEARCH));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_FOWNER));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_FSETID));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_KILL));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_SETGID));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_SETUID));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_SETPCAP));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_LINUX_IMMUTABLE));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_NET_BIND_SERVICE));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_NET_BROADCAST));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_NET_ADMIN));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_NET_RAW));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_IPC_LOCK));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_IPC_OWNER));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_SYS_MODULE));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_SYS_RAWIO));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_SYS_CHROOT));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_SYS_PTRACE));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_SYS_PACCT));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_SYS_ADMIN));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_SYS_BOOT));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_SYS_NICE));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_SYS_RESOURCE));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_SYS_TIME));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_SYS_TTY_CONFIG));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_MKNOD));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_LEASE));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_AUDIT_WRITE));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_AUDIT_CONTROL));
+ assertEquals(0, CAP_TO_INDEX(OsConstants.CAP_SETFCAP));
+ assertEquals(1, CAP_TO_INDEX(OsConstants.CAP_MAC_OVERRIDE));
+ assertEquals(1, CAP_TO_INDEX(OsConstants.CAP_MAC_ADMIN));
+ assertEquals(1, CAP_TO_INDEX(OsConstants.CAP_SYSLOG));
+ assertEquals(1, CAP_TO_INDEX(OsConstants.CAP_WAKE_ALARM));
+ assertEquals(1, CAP_TO_INDEX(OsConstants.CAP_BLOCK_SUSPEND));
+ }
+
+ @Test
+ public void test_CAP_TO_MASK() {
+ assertEquals(1 << 0, CAP_TO_MASK(OsConstants.CAP_CHOWN));
+ assertEquals(1 << 1, CAP_TO_MASK(OsConstants.CAP_DAC_OVERRIDE));
+ assertEquals(1 << 2, CAP_TO_MASK(OsConstants.CAP_DAC_READ_SEARCH));
+ assertEquals(1 << 3, CAP_TO_MASK(OsConstants.CAP_FOWNER));
+ assertEquals(1 << 4, CAP_TO_MASK(OsConstants.CAP_FSETID));
+ assertEquals(1 << 5, CAP_TO_MASK(OsConstants.CAP_KILL));
+ assertEquals(1 << 6, CAP_TO_MASK(OsConstants.CAP_SETGID));
+ assertEquals(1 << 7, CAP_TO_MASK(OsConstants.CAP_SETUID));
+ assertEquals(1 << 8, CAP_TO_MASK(OsConstants.CAP_SETPCAP));
+ assertEquals(1 << 9, CAP_TO_MASK(OsConstants.CAP_LINUX_IMMUTABLE));
+ assertEquals(1 << 10, CAP_TO_MASK(OsConstants.CAP_NET_BIND_SERVICE));
+ assertEquals(1 << 11, CAP_TO_MASK(OsConstants.CAP_NET_BROADCAST));
+ assertEquals(1 << 12, CAP_TO_MASK(OsConstants.CAP_NET_ADMIN));
+ assertEquals(1 << 13, CAP_TO_MASK(OsConstants.CAP_NET_RAW));
+ assertEquals(1 << 14, CAP_TO_MASK(OsConstants.CAP_IPC_LOCK));
+ assertEquals(1 << 15, CAP_TO_MASK(OsConstants.CAP_IPC_OWNER));
+ assertEquals(1 << 16, CAP_TO_MASK(OsConstants.CAP_SYS_MODULE));
+ assertEquals(1 << 17, CAP_TO_MASK(OsConstants.CAP_SYS_RAWIO));
+ assertEquals(1 << 18, CAP_TO_MASK(OsConstants.CAP_SYS_CHROOT));
+ assertEquals(1 << 19, CAP_TO_MASK(OsConstants.CAP_SYS_PTRACE));
+ assertEquals(1 << 20, CAP_TO_MASK(OsConstants.CAP_SYS_PACCT));
+ assertEquals(1 << 21, CAP_TO_MASK(OsConstants.CAP_SYS_ADMIN));
+ assertEquals(1 << 22, CAP_TO_MASK(OsConstants.CAP_SYS_BOOT));
+ assertEquals(1 << 23, CAP_TO_MASK(OsConstants.CAP_SYS_NICE));
+ assertEquals(1 << 24, CAP_TO_MASK(OsConstants.CAP_SYS_RESOURCE));
+ assertEquals(1 << 25, CAP_TO_MASK(OsConstants.CAP_SYS_TIME));
+ assertEquals(1 << 26, CAP_TO_MASK(OsConstants.CAP_SYS_TTY_CONFIG));
+ assertEquals(1 << 27, CAP_TO_MASK(OsConstants.CAP_MKNOD));
+ assertEquals(1 << 28, CAP_TO_MASK(OsConstants.CAP_LEASE));
+ assertEquals(1 << 29, CAP_TO_MASK(OsConstants.CAP_AUDIT_WRITE));
+ assertEquals(1 << 30, CAP_TO_MASK(OsConstants.CAP_AUDIT_CONTROL));
+ assertEquals(1 << 31, CAP_TO_MASK(OsConstants.CAP_SETFCAP));
+ assertEquals(1 << 0, CAP_TO_MASK(OsConstants.CAP_MAC_OVERRIDE));
+ assertEquals(1 << 1, CAP_TO_MASK(OsConstants.CAP_MAC_ADMIN));
+ assertEquals(1 << 2, CAP_TO_MASK(OsConstants.CAP_SYSLOG));
+ assertEquals(1 << 3, CAP_TO_MASK(OsConstants.CAP_WAKE_ALARM));
+ assertEquals(1 << 4, CAP_TO_MASK(OsConstants.CAP_BLOCK_SUSPEND));
+ }
+
+ @Test
+ public void test_S_ISLNK() {
+ assertTrue(S_ISLNK(OsConstants.S_IFLNK));
+
+ assertFalse(S_ISLNK(OsConstants.S_IFBLK));
+ assertFalse(S_ISLNK(OsConstants.S_IFCHR));
+ assertFalse(S_ISLNK(OsConstants.S_IFDIR));
+ assertFalse(S_ISLNK(OsConstants.S_IFIFO));
+ assertFalse(S_ISLNK(OsConstants.S_IFMT));
+ assertFalse(S_ISLNK(OsConstants.S_IFREG));
+ assertFalse(S_ISLNK(OsConstants.S_IFSOCK));
+ assertFalse(S_ISLNK(OsConstants.S_IRGRP));
+ assertFalse(S_ISLNK(OsConstants.S_IROTH));
+ assertFalse(S_ISLNK(OsConstants.S_IRUSR));
+ assertFalse(S_ISLNK(OsConstants.S_IRWXG));
+ assertFalse(S_ISLNK(OsConstants.S_IRWXO));
+ assertFalse(S_ISLNK(OsConstants.S_IRWXU));
+ assertFalse(S_ISLNK(OsConstants.S_ISGID));
+ assertFalse(S_ISLNK(OsConstants.S_ISUID));
+ assertFalse(S_ISLNK(OsConstants.S_ISVTX));
+ assertFalse(S_ISLNK(OsConstants.S_IWGRP));
+ assertFalse(S_ISLNK(OsConstants.S_IWOTH));
+ assertFalse(S_ISLNK(OsConstants.S_IWUSR));
+ assertFalse(S_ISLNK(OsConstants.S_IXGRP));
+ assertFalse(S_ISLNK(OsConstants.S_IXOTH));
+ assertFalse(S_ISLNK(OsConstants.S_IXUSR));
+ }
+
+ @Test
+ public void test_S_ISREG() {
+ assertTrue(S_ISREG(OsConstants.S_IFREG));
+
+ assertFalse(S_ISREG(OsConstants.S_IFBLK));
+ assertFalse(S_ISREG(OsConstants.S_IFCHR));
+ assertFalse(S_ISREG(OsConstants.S_IFDIR));
+ assertFalse(S_ISREG(OsConstants.S_IFIFO));
+ assertFalse(S_ISREG(OsConstants.S_IFLNK));
+ assertFalse(S_ISREG(OsConstants.S_IFMT));
+ assertFalse(S_ISREG(OsConstants.S_IFSOCK));
+ assertFalse(S_ISREG(OsConstants.S_IRGRP));
+ assertFalse(S_ISREG(OsConstants.S_IROTH));
+ assertFalse(S_ISREG(OsConstants.S_IRUSR));
+ assertFalse(S_ISREG(OsConstants.S_IRWXG));
+ assertFalse(S_ISREG(OsConstants.S_IRWXO));
+ assertFalse(S_ISREG(OsConstants.S_IRWXU));
+ assertFalse(S_ISREG(OsConstants.S_ISGID));
+ assertFalse(S_ISREG(OsConstants.S_ISUID));
+ assertFalse(S_ISREG(OsConstants.S_ISVTX));
+ assertFalse(S_ISREG(OsConstants.S_IWGRP));
+ assertFalse(S_ISREG(OsConstants.S_IWOTH));
+ assertFalse(S_ISREG(OsConstants.S_IWUSR));
+ assertFalse(S_ISREG(OsConstants.S_IXGRP));
+ assertFalse(S_ISREG(OsConstants.S_IXOTH));
+ assertFalse(S_ISREG(OsConstants.S_IXUSR));
+ }
+
+ @Test
+ public void test_S_ISDIR() {
+ assertTrue(S_ISDIR(OsConstants.S_IFDIR));
+
+ assertFalse(S_ISDIR(OsConstants.S_IFBLK));
+ assertFalse(S_ISDIR(OsConstants.S_IFCHR));
+ assertFalse(S_ISDIR(OsConstants.S_IFIFO));
+ assertFalse(S_ISDIR(OsConstants.S_IFLNK));
+ assertFalse(S_ISDIR(OsConstants.S_IFMT));
+ assertFalse(S_ISDIR(OsConstants.S_IFREG));
+ assertFalse(S_ISDIR(OsConstants.S_IFSOCK));
+ assertFalse(S_ISDIR(OsConstants.S_IRGRP));
+ assertFalse(S_ISDIR(OsConstants.S_IROTH));
+ assertFalse(S_ISDIR(OsConstants.S_IRUSR));
+ assertFalse(S_ISDIR(OsConstants.S_IRWXG));
+ assertFalse(S_ISDIR(OsConstants.S_IRWXO));
+ assertFalse(S_ISDIR(OsConstants.S_IRWXU));
+ assertFalse(S_ISDIR(OsConstants.S_ISGID));
+ assertFalse(S_ISDIR(OsConstants.S_ISUID));
+ assertFalse(S_ISDIR(OsConstants.S_ISVTX));
+ assertFalse(S_ISDIR(OsConstants.S_IWGRP));
+ assertFalse(S_ISDIR(OsConstants.S_IWOTH));
+ assertFalse(S_ISDIR(OsConstants.S_IWUSR));
+ assertFalse(S_ISDIR(OsConstants.S_IXGRP));
+ assertFalse(S_ISDIR(OsConstants.S_IXOTH));
+ assertFalse(S_ISDIR(OsConstants.S_IXUSR));
+ }
+
+ @Test
+ public void test_S_ISCHR() {
+ assertTrue(S_ISCHR(OsConstants.S_IFCHR));
+
+ assertFalse(S_ISCHR(OsConstants.S_IFBLK));
+ assertFalse(S_ISCHR(OsConstants.S_IFDIR));
+ assertFalse(S_ISCHR(OsConstants.S_IFIFO));
+ assertFalse(S_ISCHR(OsConstants.S_IFLNK));
+ assertFalse(S_ISCHR(OsConstants.S_IFMT));
+ assertFalse(S_ISCHR(OsConstants.S_IFREG));
+ assertFalse(S_ISCHR(OsConstants.S_IFSOCK));
+ assertFalse(S_ISCHR(OsConstants.S_IRGRP));
+ assertFalse(S_ISCHR(OsConstants.S_IROTH));
+ assertFalse(S_ISCHR(OsConstants.S_IRUSR));
+ assertFalse(S_ISCHR(OsConstants.S_IRWXG));
+ assertFalse(S_ISCHR(OsConstants.S_IRWXO));
+ assertFalse(S_ISCHR(OsConstants.S_IRWXU));
+ assertFalse(S_ISCHR(OsConstants.S_ISGID));
+ assertFalse(S_ISCHR(OsConstants.S_ISUID));
+ assertFalse(S_ISCHR(OsConstants.S_ISVTX));
+ assertFalse(S_ISCHR(OsConstants.S_IWGRP));
+ assertFalse(S_ISCHR(OsConstants.S_IWOTH));
+ assertFalse(S_ISCHR(OsConstants.S_IWUSR));
+ assertFalse(S_ISCHR(OsConstants.S_IXGRP));
+ assertFalse(S_ISCHR(OsConstants.S_IXOTH));
+ assertFalse(S_ISCHR(OsConstants.S_IXUSR));
+ }
+
+ @Test
+ public void test_S_ISBLK() {
+ assertTrue (S_ISBLK(OsConstants.S_IFBLK));
+
+ assertFalse(S_ISBLK(OsConstants.S_IFCHR));
+ assertFalse(S_ISBLK(OsConstants.S_IFDIR));
+ assertFalse(S_ISBLK(OsConstants.S_IFIFO));
+ assertFalse(S_ISBLK(OsConstants.S_IFLNK));
+ assertFalse(S_ISBLK(OsConstants.S_IFMT));
+ assertFalse(S_ISBLK(OsConstants.S_IFREG));
+ assertFalse(S_ISBLK(OsConstants.S_IFSOCK));
+ assertFalse(S_ISBLK(OsConstants.S_IRGRP));
+ assertFalse(S_ISBLK(OsConstants.S_IROTH));
+ assertFalse(S_ISBLK(OsConstants.S_IRUSR));
+ assertFalse(S_ISBLK(OsConstants.S_IRWXG));
+ assertFalse(S_ISBLK(OsConstants.S_IRWXO));
+ assertFalse(S_ISBLK(OsConstants.S_IRWXU));
+ assertFalse(S_ISBLK(OsConstants.S_ISGID));
+ assertFalse(S_ISBLK(OsConstants.S_ISUID));
+ assertFalse(S_ISBLK(OsConstants.S_ISVTX));
+ assertFalse(S_ISBLK(OsConstants.S_IWGRP));
+ assertFalse(S_ISBLK(OsConstants.S_IWOTH));
+ assertFalse(S_ISBLK(OsConstants.S_IWUSR));
+ assertFalse(S_ISBLK(OsConstants.S_IXGRP));
+ assertFalse(S_ISBLK(OsConstants.S_IXOTH));
+ assertFalse(S_ISBLK(OsConstants.S_IXUSR));
+ }
+
+ @Test
+ public void test_S_ISFIFO() {
+ assertTrue(S_ISFIFO(OsConstants.S_IFIFO));
+
+ assertFalse(S_ISFIFO(OsConstants.S_IFBLK));
+ assertFalse(S_ISFIFO(OsConstants.S_IFCHR));
+ assertFalse(S_ISFIFO(OsConstants.S_IFDIR));
+ assertFalse(S_ISFIFO(OsConstants.S_IFLNK));
+ assertFalse(S_ISFIFO(OsConstants.S_IFMT));
+ assertFalse(S_ISFIFO(OsConstants.S_IFREG));
+ assertFalse(S_ISFIFO(OsConstants.S_IFSOCK));
+ assertFalse(S_ISFIFO(OsConstants.S_IRGRP));
+ assertFalse(S_ISFIFO(OsConstants.S_IROTH));
+ assertFalse(S_ISFIFO(OsConstants.S_IRUSR));
+ assertFalse(S_ISFIFO(OsConstants.S_IRWXG));
+ assertFalse(S_ISFIFO(OsConstants.S_IRWXO));
+ assertFalse(S_ISFIFO(OsConstants.S_IRWXU));
+ assertFalse(S_ISFIFO(OsConstants.S_ISGID));
+ assertFalse(S_ISFIFO(OsConstants.S_ISUID));
+ assertFalse(S_ISFIFO(OsConstants.S_ISVTX));
+ assertFalse(S_ISFIFO(OsConstants.S_IWGRP));
+ assertFalse(S_ISFIFO(OsConstants.S_IWOTH));
+ assertFalse(S_ISFIFO(OsConstants.S_IWUSR));
+ assertFalse(S_ISFIFO(OsConstants.S_IXGRP));
+ assertFalse(S_ISFIFO(OsConstants.S_IXOTH));
+ assertFalse(S_ISFIFO(OsConstants.S_IXUSR));
+ }
+
+ @Test
+ public void test_S_ISSOCK() {
+ assertTrue(S_ISSOCK(OsConstants.S_IFSOCK));
+
+ assertFalse(S_ISSOCK(OsConstants.S_IFBLK));
+ assertFalse(S_ISSOCK(OsConstants.S_IFCHR));
+ assertFalse(S_ISSOCK(OsConstants.S_IFDIR));
+ assertFalse(S_ISSOCK(OsConstants.S_IFIFO));
+ assertFalse(S_ISSOCK(OsConstants.S_IFLNK));
+ assertFalse(S_ISSOCK(OsConstants.S_IFMT));
+ assertFalse(S_ISSOCK(OsConstants.S_IFREG));
+ assertFalse(S_ISSOCK(OsConstants.S_IRGRP));
+ assertFalse(S_ISSOCK(OsConstants.S_IROTH));
+ assertFalse(S_ISSOCK(OsConstants.S_IRUSR));
+ assertFalse(S_ISSOCK(OsConstants.S_IRWXG));
+ assertFalse(S_ISSOCK(OsConstants.S_IRWXO));
+ assertFalse(S_ISSOCK(OsConstants.S_IRWXU));
+ assertFalse(S_ISSOCK(OsConstants.S_ISGID));
+ assertFalse(S_ISSOCK(OsConstants.S_ISUID));
+ assertFalse(S_ISSOCK(OsConstants.S_ISVTX));
+ assertFalse(S_ISSOCK(OsConstants.S_IWGRP));
+ assertFalse(S_ISSOCK(OsConstants.S_IWOTH));
+ assertFalse(S_ISSOCK(OsConstants.S_IWUSR));
+ assertFalse(S_ISSOCK(OsConstants.S_IXGRP));
+ assertFalse(S_ISSOCK(OsConstants.S_IXOTH));
+ assertFalse(S_ISSOCK(OsConstants.S_IXUSR));
+ }
+
+ @Test
+ public void test_WEXITSTATUS() {
+ assertEquals(0, WEXITSTATUS(0x0000));
+ assertEquals(0, WEXITSTATUS(0x00DE));
+ assertEquals(0xF0, WEXITSTATUS(0xF000));
+ assertEquals(0xAB, WEXITSTATUS(0xAB12));
+ }
+
+ @Test
+ public void test_WCOREDUMP() {
+ assertFalse(WCOREDUMP(0));
+ assertTrue(WCOREDUMP(0x80));
+ }
+
+ @Test
+ public void test_WTERMSIG() {
+ assertEquals(0, WTERMSIG(0));
+ assertEquals(0x7f, WTERMSIG(0x7f));
+ }
+
+ @Test
+ public void test_WSTOPSIG() {
+ assertEquals(0, WSTOPSIG(0x0000));
+ assertEquals(0, WSTOPSIG(0x00DE));
+ assertEquals(0xF0, WSTOPSIG(0xF000));
+ assertEquals(0xAB, WSTOPSIG(0xAB12));
+ }
+
+
+ @Test
+ public void test_WIFEXITED() {
+ assertTrue(WIFEXITED(0));
+ assertFalse(WIFEXITED(0x7f));
+ }
+
+ @Test
+ public void test_WIFSTOPPED() {
+ assertFalse(WIFSTOPPED(0));
+ assertTrue(WIFSTOPPED(0x7f));
+ }
+
+ @Test
+ public void test_WIFSIGNALED() {
+ assertFalse(WIFSIGNALED(0));
+ assertTrue(WIFSIGNALED(1));
+ }
+}
diff --git a/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java b/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java
new file mode 100644
index 0000000..b5038e6
--- /dev/null
+++ b/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.ravenwood.runtimetest;
+
+import static org.junit.Assert.assertEquals;
+
+import android.system.Os;
+import android.system.OsConstants;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.ravenwood.common.JvmWorkaround;
+import com.android.ravenwood.common.RavenwoodRuntimeNative;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+@RunWith(AndroidJUnit4.class)
+public class OsTest {
+ public interface ConsumerWithThrow<T> {
+ void accept(T var1) throws Exception;
+ }
+
+ private void withTestFile(ConsumerWithThrow<FileDescriptor> consumer) throws Exception {
+ File file = File.createTempFile("osTest", "bin");
+ try (var raf = new RandomAccessFile(file, "rw")) {
+ var fd = raf.getFD();
+
+ try (var os = new FileOutputStream(fd)) {
+ os.write(1);
+ os.write(2);
+ os.write(3);
+ os.write(4);
+
+ consumer.accept(fd);
+ }
+ }
+ }
+
+ @Test
+ public void testLseek() throws Exception {
+ withTestFile((fd) -> {
+ assertEquals(4, Os.lseek(fd, 4, OsConstants.SEEK_SET));
+ assertEquals(4, Os.lseek(fd, 0, OsConstants.SEEK_CUR));
+ assertEquals(6, Os.lseek(fd, 2, OsConstants.SEEK_CUR));
+ });
+ }
+
+ @Test
+ public void testDup() throws Exception {
+ withTestFile((fd) -> {
+ var dup = Os.dup(fd);
+
+ checkAreDup(fd, dup);
+ });
+ }
+
+ @Test
+ public void testPipe2() throws Exception {
+ var fds = Os.pipe2(0);
+
+ write(fds[1], 123);
+ assertEquals(123, read(fds[0]));
+ }
+
+ @Test
+ public void testFcntlInt() throws Exception {
+ withTestFile((fd) -> {
+ var dupInt = Os.fcntlInt(fd, 0, 0);
+
+ var dup = new FileDescriptor();
+ JvmWorkaround.getInstance().setFdInt(dup, dupInt);
+
+ checkAreDup(fd, dup);
+ });
+ }
+
+ private static void write(FileDescriptor fd, int oneByte) throws IOException {
+ // Create a dup to avoid closing the FD.
+ try (var dup = new FileOutputStream(RavenwoodRuntimeNative.dup(fd))) {
+ dup.write(oneByte);
+ }
+ }
+
+ private static int read(FileDescriptor fd) throws IOException {
+ // Create a dup to avoid closing the FD.
+ try (var dup = new FileInputStream(RavenwoodRuntimeNative.dup(fd))) {
+ return dup.read();
+ }
+ }
+
+ private static void checkAreDup(FileDescriptor fd1, FileDescriptor fd2) throws Exception {
+ assertEquals(4, Os.lseek(fd1, 4, OsConstants.SEEK_SET));
+ assertEquals(4, Os.lseek(fd1, 0, OsConstants.SEEK_CUR));
+
+ // Dup'ed FD shares the same position.
+ assertEquals(4, Os.lseek(fd2, 0, OsConstants.SEEK_CUR));
+
+ assertEquals(6, Os.lseek(fd1, 2, OsConstants.SEEK_CUR));
+ assertEquals(6, Os.lseek(fd2, 0, OsConstants.SEEK_CUR));
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/SaveEventLogger.java b/services/autofill/java/com/android/server/autofill/SaveEventLogger.java
index b7f12ad..c41a8cd 100644
--- a/services/autofill/java/com/android/server/autofill/SaveEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/SaveEventLogger.java
@@ -23,6 +23,7 @@
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_NONE;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_NO_SAVE_INFO;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_NO_VALUE_CHANGED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_SCREEN_HAS_CREDMAN_FIELD;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_SESSION_DESTROYED;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_WITH_DELAY_SAVE_FLAG;
@@ -112,6 +113,8 @@
AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_SESSION_DESTROYED;
public static final int NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG =
AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG;
+ public static final int NO_SAVE_REASON_SCREEN_HAS_CREDMAN_FIELD =
+ AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_SCREEN_HAS_CREDMAN_FIELD;
public static final long UNINITIATED_TIMESTAMP = Long.MIN_VALUE;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index a8b1235..c46464b 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -89,6 +89,7 @@
import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_NONE;
import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_NO_SAVE_INFO;
import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_NO_VALUE_CHANGED;
+import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_SCREEN_HAS_CREDMAN_FIELD;
import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_SESSION_DESTROYED;
import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_WITH_DELAY_SAVE_FLAG;
import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG;
@@ -3673,6 +3674,8 @@
Slog.v(TAG, "Call to Session#showSaveLocked() rejected - "
+ "there is credman field in screen");
}
+ mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_SCREEN_HAS_CREDMAN_FIELD);
+ mSaveEventLogger.logAndEndEvent();
return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
Event.NO_SAVE_UI_REASON_NONE);
}
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index b414b25..2d99c96 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -270,7 +270,8 @@
PackageManagerInternal.class);
RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
mBackupManagerService.getPackageManager(), allowApks, info, signatures,
- pmi, mUserId, mBackupEligibilityRules);
+ pmi, mUserId, mBackupEligibilityRules,
+ mBackupManagerService.getContext());
mManifestSignatures.put(info.packageName, signatures);
mPackagePolicies.put(pkg, restorePolicy);
mPackageInstallers.put(pkg, info.installerPackageName);
diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
index 78a9952..4860a27 100644
--- a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
+++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
@@ -31,6 +31,7 @@
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_MISSING_SIGNATURE;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_RESTORE_ANY_VERSION;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_SYSTEM_APP_NO_AGENT;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_V_TO_U_RESTORE_PKG_ELIGIBLE;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSIONS_MATCH;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER;
@@ -53,17 +54,22 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
+import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Slog;
import com.android.server.backup.FileMetadata;
+import com.android.server.backup.Flags;
import com.android.server.backup.restore.RestorePolicy;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
/**
* Utility methods to read backup tar file.
@@ -390,7 +396,7 @@
boolean allowApks, FileMetadata info, Signature[] signatures,
PackageManagerInternal pmi, int userId, Context context) {
return chooseRestorePolicy(packageManager, allowApks, info, signatures, pmi, userId,
- BackupEligibilityRules.forBackup(packageManager, pmi, userId, context));
+ BackupEligibilityRules.forBackup(packageManager, pmi, userId, context), context);
}
/**
@@ -406,7 +412,8 @@
*/
public RestorePolicy chooseRestorePolicy(PackageManager packageManager,
boolean allowApks, FileMetadata info, Signature[] signatures,
- PackageManagerInternal pmi, int userId, BackupEligibilityRules eligibilityRules) {
+ PackageManagerInternal pmi, int userId, BackupEligibilityRules eligibilityRules,
+ Context context) {
if (signatures == null) {
return RestorePolicy.IGNORE;
}
@@ -448,6 +455,16 @@
pkgInfo,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
null);
+ } else if (isAllowlistedForVToURestore(info, pkgInfo, userId, context)) {
+ Slog.i(TAG, "Performing a V to U downgrade; package: "
+ + info.packageName
+ + " is allowlisted");
+ policy = RestorePolicy.ACCEPT;
+ mBackupManagerMonitorEventSender.monitorEvent(
+ LOG_EVENT_ID_V_TO_U_RESTORE_PKG_ELIGIBLE,
+ pkgInfo,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
} else {
// The data is from a newer version of the app than
// is presently installed. That means we can only
@@ -751,6 +768,36 @@
return true;
}
+ // checks the sdk of the target/source device for a B&R operation.
+ // system components can opt in of V->U restore via allowlist.
+ @SuppressWarnings("AndroidFrameworkCompatChange")
+ private boolean isAllowlistedForVToURestore(FileMetadata backupFileInfo,
+ PackageInfo installedPackageInfo,
+ int userId, Context context) {
+ // We assume that the package version matches the sdk (e.g. version 35 means V).
+ // This is true for most of the system components ( and it is specifically true for those
+ // that are in the allowlist)
+ // In order to check if this is a V to U transfer we check if the package version from the
+ // backup is 35 and on the target is 34.
+ // We don't need to check the V to U denylist here since a package can only make it
+ // to TarBackupReader if allowed and not denied (from PerformUnifiedRestoreTask)
+
+ String vToUAllowlist = getVToUAllowlist(context, userId);
+ List<String> mVToUAllowlist = Arrays.asList(vToUAllowlist.split(","));
+ return Flags.enableVToURestoreForSystemComponentsInAllowlist()
+ && (installedPackageInfo.getLongVersionCode()
+ == Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ && (backupFileInfo.version > Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ && (mVToUAllowlist.contains(installedPackageInfo.packageName));
+ }
+
+ private String getVToUAllowlist(Context context, int userId) {
+ return Settings.Secure.getStringForUser(
+ context.getContentResolver(),
+ Settings.Secure.V_TO_U_RESTORE_ALLOWLIST,
+ userId);
+ }
+
private static long extractRadix(byte[] data, int offset, int maxChars, int radix)
throws IOException {
long value = 0;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4c8f416..f7278e9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -40,6 +40,8 @@
import static android.app.ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED;
import static android.app.ActivityManager.RESTRICTION_REASON_DEFAULT;
import static android.app.ActivityManager.RESTRICTION_REASON_USAGE;
+import static android.app.ActivityManager.RESTRICTION_SOURCE_SYSTEM;
+import static android.app.ActivityManager.RESTRICTION_SOURCE_USER;
import static android.app.ActivityManager.StopUserOnSwitch;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
@@ -5148,7 +5150,7 @@
if (android.app.Flags.appRestrictionsApi() && wasForceStopped) {
noteAppRestrictionEnabled(app.info.packageName, app.uid,
RESTRICTION_LEVEL_FORCE_STOPPED, false,
- RESTRICTION_REASON_USAGE, "unknown", 0L);
+ RESTRICTION_REASON_USAGE, "unknown", RESTRICTION_SOURCE_USER, 0L);
}
if (!sendBroadcast) {
@@ -6023,46 +6025,47 @@
}
synchronized (mProcLock) {
- synchronized (mPidsSelfLocked) {
- int newestTimeIndex = -1;
- long newestTime = Long.MIN_VALUE;
- for (int i = 0; i < pids.length; i++) {
- ProcessRecord pr = mPidsSelfLocked.get(pids[i]);
- if (pr != null) {
- final long pendingTopTime =
- mPendingStartActivityUids.getPendingTopPidTime(pr.uid, pids[i]);
- if (pendingTopTime != PendingStartActivityUids.INVALID_TIME) {
- // The uid in mPendingStartActivityUids gets the TOP process state.
- states[i] = PROCESS_STATE_TOP;
- if (scores != null) {
- // The uid in mPendingStartActivityUids gets a better score.
- scores[i] = ProcessList.FOREGROUND_APP_ADJ - 1;
- }
- if (pendingTopTime > newestTime) {
- newestTimeIndex = i;
- newestTime = pendingTopTime;
- }
- } else {
- states[i] = pr.mState.getCurProcState();
- if (scores != null) {
- scores[i] = pr.mState.getCurAdj();
- }
+ int newestTimeIndex = -1;
+ long newestTime = Long.MIN_VALUE;
+ for (int i = 0; i < pids.length; i++) {
+ final ProcessRecord pr;
+ synchronized (mPidsSelfLocked) {
+ pr = mPidsSelfLocked.get(pids[i]);
+ }
+ if (pr != null) {
+ final long pendingTopTime =
+ mPendingStartActivityUids.getPendingTopPidTime(pr.uid, pids[i]);
+ if (pendingTopTime != PendingStartActivityUids.INVALID_TIME) {
+ // The uid in mPendingStartActivityUids gets the TOP process state.
+ states[i] = PROCESS_STATE_TOP;
+ if (scores != null) {
+ // The uid in mPendingStartActivityUids gets a better score.
+ scores[i] = ProcessList.FOREGROUND_APP_ADJ - 1;
+ }
+ if (pendingTopTime > newestTime) {
+ newestTimeIndex = i;
+ newestTime = pendingTopTime;
}
} else {
- states[i] = PROCESS_STATE_NONEXISTENT;
+ states[i] = pr.mState.getCurProcState();
if (scores != null) {
- scores[i] = ProcessList.INVALID_ADJ;
+ scores[i] = pr.mState.getCurAdj();
}
}
- }
- // The uid with the newest timestamp in mPendingStartActivityUids gets the best
- // score.
- if (newestTimeIndex != -1) {
+ } else {
+ states[i] = PROCESS_STATE_NONEXISTENT;
if (scores != null) {
- scores[newestTimeIndex] = ProcessList.FOREGROUND_APP_ADJ - 2;
+ scores[i] = ProcessList.INVALID_ADJ;
}
}
}
+ // The uid with the newest timestamp in mPendingStartActivityUids gets the best
+ // score.
+ if (newestTimeIndex != -1) {
+ if (scores != null) {
+ scores[newestTimeIndex] = ProcessList.FOREGROUND_APP_ADJ - 2;
+ }
+ }
}
}
@@ -10304,7 +10307,8 @@
command.add("/system/bin/logcat");
command.add("-v");
// This adds a timestamp and thread info to each log line.
- command.add("threadtime");
+ // Also change the timestamps to use UTC time.
+ command.add("threadtime,UTC");
for (String buffer : buffers) {
command.add("-b");
command.add(buffer);
@@ -14399,7 +14403,8 @@
if (wasStopped) {
noteAppRestrictionEnabled(app.packageName, app.uid,
RESTRICTION_LEVEL_FORCE_STOPPED, false,
- RESTRICTION_REASON_DEFAULT, "restore", 0L);
+ RESTRICTION_REASON_DEFAULT, "restore",
+ RESTRICTION_SOURCE_SYSTEM, 0L);
}
} catch (NameNotFoundException e) {
Slog.w(TAG, "No such package", e);
@@ -16953,6 +16958,18 @@
int userId = UserHandle.getCallingUserId();
+ if (UserManager.isVisibleBackgroundUsersEnabled() && userId != getCurrentUserId()) {
+ // The check is added mainly for auto devices. On auto devices, it is possible that
+ // multiple users are visible simultaneously using visible background users.
+ // In such cases, it is desired that only the current user (not the visible background
+ // user) can change the locale and other persistent settings of the device.
+ Slog.w(TAG, "Only current user is allowed to update persistent configuration if "
+ + "visible background users are enabled. Current User" + getCurrentUserId()
+ + ". Calling User: " + userId);
+ throw new SecurityException("Only current user is allowed to update persistent "
+ + "configuration.");
+ }
+
mActivityTaskManager.updatePersistentConfiguration(values, userId);
}
@@ -20304,12 +20321,14 @@
* Log the reason for changing an app restriction. Purely used for logging purposes and does not
* cause any change to app state.
*
- * @see ActivityManager#noteAppRestrictionEnabled(String, int, int, boolean, int, String, long)
+ * @see ActivityManager#noteAppRestrictionEnabled(String, int, int, boolean, int,
+ * String, int, long)
*/
@Override
public void noteAppRestrictionEnabled(String packageName, int uid,
@RestrictionLevel int restrictionType, boolean enabled,
- @ActivityManager.RestrictionReason int reason, String subReason, long threshold) {
+ @ActivityManager.RestrictionReason int reason, String subReason,
+ @ActivityManager.RestrictionSource int source, long threshold) {
if (!android.app.Flags.appRestrictionsApi()) return;
enforceCallingPermission(android.Manifest.permission.DEVICE_POWER,
@@ -20322,7 +20341,7 @@
uid = mPackageManagerInt.getPackageUid(packageName, 0, userId);
}
mAppRestrictionController.noteAppRestrictionEnabled(packageName, uid, restrictionType,
- enabled, reason, subReason, threshold);
+ enabled, reason, subReason, source, threshold);
} finally {
Binder.restoreCallingIdentity(callingId);
}
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index f5f1928..4a31fd1 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -31,11 +31,10 @@
import static android.app.ActivityManager.RESTRICTION_LEVEL_USER_LAUNCH_ONLY;
import static android.app.ActivityManager.RESTRICTION_REASON_DEFAULT;
import static android.app.ActivityManager.RESTRICTION_REASON_DORMANT;
-import static android.app.ActivityManager.RESTRICTION_REASON_REMOTE_TRIGGER;
+import static android.app.ActivityManager.RESTRICTION_REASON_POLICY;
import static android.app.ActivityManager.RESTRICTION_REASON_SYSTEM_HEALTH;
import static android.app.ActivityManager.RESTRICTION_REASON_USAGE;
import static android.app.ActivityManager.RESTRICTION_REASON_USER;
-import static android.app.ActivityManager.RESTRICTION_REASON_USER_NUDGED;
import static android.app.ActivityManager.RESTRICTION_SUBREASON_MAX_LENGTH;
import static android.app.ActivityManager.UID_OBSERVER_ACTIVE;
import static android.app.ActivityManager.UID_OBSERVER_GONE;
@@ -103,6 +102,7 @@
import android.app.ActivityManager;
import android.app.ActivityManager.RestrictionLevel;
import android.app.ActivityManager.RestrictionReason;
+import android.app.ActivityManager.RestrictionSource;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerInternal.AppBackgroundRestrictionListener;
import android.app.AppOpsManager;
@@ -2378,7 +2378,8 @@
*/
public void noteAppRestrictionEnabled(String packageName, int uid,
@RestrictionLevel int restrictionType, boolean enabled,
- @RestrictionReason int reason, String subReason, long threshold) {
+ @RestrictionReason int reason, String subReason, @RestrictionSource int source,
+ long threshold) {
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, (enabled ? "restricted " : "unrestricted ") + packageName + " to "
+ restrictionType + " reason=" + reason + ", subReason=" + subReason
@@ -2397,7 +2398,8 @@
enabled,
getRestrictionChangeReasonStatsd(reason, subReason),
subReason,
- threshold);
+ threshold,
+ source);
}
private int getRestrictionTypeStatsd(@RestrictionLevel int level) {
@@ -2433,12 +2435,10 @@
FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USAGE;
case RESTRICTION_REASON_USER ->
FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USER;
- case RESTRICTION_REASON_USER_NUDGED ->
- FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USER_NUDGED;
case RESTRICTION_REASON_SYSTEM_HEALTH ->
FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_SYSTEM_HEALTH;
- case RESTRICTION_REASON_REMOTE_TRIGGER ->
- FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_REMOTE_TRIGGER;
+ case RESTRICTION_REASON_POLICY ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_POLICY;
default ->
FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_OTHER;
};
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 79a8518..a8227fa 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -83,6 +83,7 @@
private static final int FOREACH_ACTION_NONE = 0;
private static final int FOREACH_ACTION_REMOVE_ITEM = 1;
private static final int FOREACH_ACTION_STOP_ITERATION = 2;
+ private static final int FOREACH_ACTION_REMOVE_AND_STOP_ITERATION = 3;
private static final String MONITORING_MODE_EMPTY_TEXT = "No records";
@@ -659,8 +660,13 @@
}
}
+ /**
+ * Run provided callback for each packake in start info dataset.
+ *
+ * @return whether the for each completed naturally, false if it was stopped manually.
+ */
@GuardedBy("mLock")
- private void forEachPackageLocked(
+ private boolean forEachPackageLocked(
BiFunction<String, SparseArray<AppStartInfoContainer>, Integer> callback) {
if (callback != null) {
ArrayMap<String, SparseArray<AppStartInfoContainer>> map = mData.getMap();
@@ -670,14 +676,17 @@
map.removeAt(i);
break;
case FOREACH_ACTION_STOP_ITERATION:
- i = 0;
- break;
+ return false;
+ case FOREACH_ACTION_REMOVE_AND_STOP_ITERATION:
+ map.removeAt(i);
+ return false;
case FOREACH_ACTION_NONE:
default:
break;
}
}
}
+ return true;
}
@GuardedBy("mLock")
@@ -870,13 +879,14 @@
}
AtomicFile af = new AtomicFile(mProcStartInfoFile);
FileOutputStream out = null;
+ boolean succeeded;
long now = System.currentTimeMillis();
try {
out = af.startWrite();
ProtoOutputStream proto = new ProtoOutputStream(out);
proto.write(AppsStartInfoProto.LAST_UPDATE_TIMESTAMP, now);
synchronized (mLock) {
- forEachPackageLocked(
+ succeeded = forEachPackageLocked(
(packageName, records) -> {
long token = proto.start(AppsStartInfoProto.PACKAGES);
proto.write(AppsStartInfoProto.Package.PACKAGE_NAME, packageName);
@@ -884,19 +894,30 @@
for (int j = 0; j < uidArraySize; j++) {
try {
records.valueAt(j)
- .writeToProto(proto, AppsStartInfoProto.Package.USERS);
+ .writeToProto(proto, AppsStartInfoProto.Package.USERS);
} catch (IOException e) {
Slog.w(TAG, "Unable to write app start info into persistent"
+ "storage: " + e);
+ // There was likely an issue with this record that won't resolve
+ // next time we try to persist so remove it. Also stop iteration
+ // as we failed the write and need to start again from scratch.
+ return AppStartInfoTracker
+ .FOREACH_ACTION_REMOVE_AND_STOP_ITERATION;
}
}
proto.end(token);
return AppStartInfoTracker.FOREACH_ACTION_NONE;
});
- mLastAppStartInfoPersistTimestamp = now;
+ if (succeeded) {
+ mLastAppStartInfoPersistTimestamp = now;
+ }
}
- proto.flush();
- af.finishWrite(out);
+ if (succeeded) {
+ proto.flush();
+ af.finishWrite(out);
+ } else {
+ af.failWrite(out);
+ }
} catch (IOException e) {
Slog.w(TAG, "Unable to write historical app start info into persistent storage: " + e);
af.failWrite(out);
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 211f952..db4840d 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -83,6 +83,8 @@
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;
@@ -98,8 +100,6 @@
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
- + " to frozen apps and got error " + err);
+ + " 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/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index fa8832c..219de70 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -36,6 +36,7 @@
import static android.os.Process.startWebView;
import static android.system.OsConstants.EAGAIN;
+import static com.android.sdksandbox.flags.Flags.selinuxInputSelector;
import static com.android.sdksandbox.flags.Flags.selinuxSdkSandboxAudit;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
@@ -2066,11 +2067,16 @@
}
}
- return app.info.seInfo
- + (TextUtils.isEmpty(app.info.seInfoUser) ? "" : app.info.seInfoUser) + extraInfo;
+ // The order of selectors in seInfo matters, the string is terminated by the word complete.
+ if (selinuxInputSelector()) {
+ return app.info.seInfo + extraInfo + TextUtils.emptyIfNull(app.info.seInfoUser);
+ } else {
+ return app.info.seInfo
+ + (TextUtils.isEmpty(app.info.seInfoUser) ? "" : app.info.seInfoUser)
+ + extraInfo;
+ }
}
-
@GuardedBy("mService")
boolean startProcessLocked(HostingRecord hostingRecord, String entryPoint, ProcessRecord app,
int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags, int mountExternal,
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index e066c23..fb6895b 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -571,6 +571,7 @@
mIActivityManager.noteAppRestrictionEnabled(
packageName, uid, ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED,
true, ActivityManager.RESTRICTION_REASON_DORMANT, null,
+ ActivityManager.RESTRICTION_SOURCE_SYSTEM,
/* TODO: fetch actual timeout - 90 days */ 90 * 24 * 60 * 60_000L);
}
// No need to log the unhibernate case as an unstop is logged already in ActivityMS
diff --git a/services/core/java/com/android/server/appop/AppOpsRecentAccessPersistence.java b/services/core/java/com/android/server/appop/AppOpsRecentAccessPersistence.java
new file mode 100644
index 0000000..238d9b9
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsRecentAccessPersistence.java
@@ -0,0 +1,403 @@
+/*
+ * 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.appop;
+
+import static android.app.AppOpsManager.extractFlagsFromKey;
+import static android.app.AppOpsManager.extractUidStateFromKey;
+import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.companion.virtual.VirtualDeviceManager;
+import android.os.Process;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * This class manages the read/write of AppOp recent accesses between memory and disk.
+ */
+final class AppOpsRecentAccessPersistence {
+ static final String TAG = "AppOpsRecentAccessPersistence";
+ final AtomicFile mRecentAccessesFile;
+ final AppOpsService mAppOpsService;
+
+ private static final String TAG_APP_OPS = "app-ops";
+ private static final String TAG_PACKAGE = "pkg";
+ private static final String TAG_UID = "uid";
+ private static final String TAG_OP = "op";
+ private static final String TAG_ATTRIBUTION_OP = "st";
+
+ private static final String ATTR_NAME = "n";
+ private static final String ATTR_ID = "id";
+ private static final String ATTR_DEVICE_ID = "dv";
+ private static final String ATTR_ACCESS_TIME = "t";
+ private static final String ATTR_REJECT_TIME = "r";
+ private static final String ATTR_ACCESS_DURATION = "d";
+ private static final String ATTR_PROXY_PACKAGE = "pp";
+ private static final String ATTR_PROXY_UID = "pu";
+ private static final String ATTR_PROXY_ATTRIBUTION_TAG = "pc";
+ private static final String ATTR_PROXY_DEVICE_ID = "pdv";
+
+ /**
+ * Version of the mRecentAccessesFile.
+ * Increment by one every time an upgrade step is added at boot, none currently exists.
+ */
+ private static final int CURRENT_VERSION = 1;
+
+ AppOpsRecentAccessPersistence(
+ @NonNull AtomicFile recentAccessesFile, @NonNull AppOpsService appOpsService) {
+ mRecentAccessesFile = recentAccessesFile;
+ mAppOpsService = appOpsService;
+ }
+
+ /**
+ * Load AppOp recent access data from disk into uidStates. The target uidStates will first clear
+ * itself before loading.
+ *
+ * @param uidStates The in-memory object where you want to populate data from disk
+ */
+ void readRecentAccesses(@NonNull SparseArray<AppOpsService.UidState> uidStates) {
+ synchronized (mRecentAccessesFile) {
+ FileInputStream stream;
+ try {
+ stream = mRecentAccessesFile.openRead();
+ } catch (FileNotFoundException e) {
+ Slog.i(
+ TAG,
+ "No existing app ops "
+ + mRecentAccessesFile.getBaseFile()
+ + "; starting empty");
+ return;
+ }
+ boolean success = false;
+ uidStates.clear();
+ mAppOpsService.mAppOpsCheckingService.clearAllModes();
+ try {
+ TypedXmlPullParser parser = Xml.resolvePullParser(stream);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // Parse next until we reach the start or end
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("no start tag found");
+ }
+
+ int outerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals(TAG_PACKAGE)) {
+ readPackage(parser, uidStates);
+ } else if (tagName.equals(TAG_UID)) {
+ // uid tag may be present during migration, don't print warning.
+ XmlUtils.skipCurrentTag(parser);
+ } else {
+ Slog.w(TAG, "Unknown element under <app-ops>: " + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ success = true;
+ } catch (IllegalStateException | NullPointerException | NumberFormatException
+ | XmlPullParserException | IOException | IndexOutOfBoundsException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } finally {
+ if (!success) {
+ uidStates.clear();
+ mAppOpsService.mAppOpsCheckingService.clearAllModes();
+ }
+ try {
+ stream.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+ }
+
+ private void readPackage(
+ TypedXmlPullParser parser, SparseArray<AppOpsService.UidState> uidStates)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ String pkgName = parser.getAttributeValue(null, ATTR_NAME);
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals(TAG_UID)) {
+ readUid(parser, pkgName, uidStates);
+ } else {
+ Slog.w(TAG, "Unknown element under <pkg>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ private void readUid(TypedXmlPullParser parser, @NonNull String pkgName,
+ SparseArray<AppOpsService.UidState> uidStates)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ int uid = parser.getAttributeInt(null, ATTR_NAME);
+ final AppOpsService.UidState uidState = mAppOpsService.new UidState(uid);
+ uidStates.put(uid, uidState);
+
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String tagName = parser.getName();
+ if (tagName.equals(TAG_OP)) {
+ readOp(parser, uidState, pkgName);
+ } else {
+ Slog.w(TAG, "Unknown element under <pkg>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ private void readOp(TypedXmlPullParser parser,
+ @NonNull AppOpsService.UidState uidState, @NonNull String pkgName)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ int opCode = parser.getAttributeInt(null, ATTR_NAME);
+ AppOpsService.Op op = mAppOpsService.new Op(uidState, pkgName, opCode, uidState.uid);
+
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String tagName = parser.getName();
+ if (tagName.equals(TAG_ATTRIBUTION_OP)) {
+ readAttributionOp(parser, op, XmlUtils.readStringAttribute(parser, ATTR_ID));
+ } else {
+ Slog.w(TAG, "Unknown element under <op>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ AppOpsService.Ops ops = uidState.pkgOps.get(pkgName);
+ if (ops == null) {
+ ops = new AppOpsService.Ops(pkgName, uidState);
+ uidState.pkgOps.put(pkgName, ops);
+ }
+ ops.put(op.op, op);
+ }
+
+ private void readAttributionOp(TypedXmlPullParser parser, @NonNull AppOpsService.Op parent,
+ @Nullable String attribution)
+ throws NumberFormatException, IOException, XmlPullParserException {
+ final long key = parser.getAttributeLong(null, ATTR_NAME);
+ final int uidState = extractUidStateFromKey(key);
+ final int opFlags = extractFlagsFromKey(key);
+
+ String deviceId = parser.getAttributeValue(null, ATTR_DEVICE_ID);
+ final long accessTime = parser.getAttributeLong(null, ATTR_ACCESS_TIME, 0);
+ final long rejectTime = parser.getAttributeLong(null, ATTR_REJECT_TIME, 0);
+ final long accessDuration = parser.getAttributeLong(null, ATTR_ACCESS_DURATION, -1);
+ final String proxyPkg = XmlUtils.readStringAttribute(parser, ATTR_PROXY_PACKAGE);
+ final int proxyUid = parser.getAttributeInt(null, ATTR_PROXY_UID, Process.INVALID_UID);
+ final String proxyAttributionTag =
+ XmlUtils.readStringAttribute(parser, ATTR_PROXY_ATTRIBUTION_TAG);
+ final String proxyDeviceId = parser.getAttributeValue(null, ATTR_PROXY_DEVICE_ID);
+
+ if (deviceId == null || Objects.equals(deviceId, "")) {
+ deviceId = PERSISTENT_DEVICE_ID_DEFAULT;
+ }
+
+ AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution, deviceId);
+
+ if (accessTime > 0) {
+ attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
+ proxyAttributionTag, proxyDeviceId, uidState, opFlags);
+ }
+ if (rejectTime > 0) {
+ attributedOp.rejected(rejectTime, uidState, opFlags);
+ }
+ }
+
+ /**
+ * Write uidStates into an XML file on the disk. It's a complete dump from memory, the XML file
+ * will be re-written.
+ *
+ * @param uidStates The in-memory object that holds all AppOp recent access data.
+ */
+ void writeRecentAccesses(SparseArray<AppOpsService.UidState> uidStates) {
+ synchronized (mRecentAccessesFile) {
+ FileOutputStream stream;
+ try {
+ stream = mRecentAccessesFile.startWrite();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to write state: " + e);
+ return;
+ }
+
+ try {
+ TypedXmlSerializer out = Xml.resolveSerializer(stream);
+ out.startDocument(null, true);
+ out.startTag(null, TAG_APP_OPS);
+ out.attributeInt(null, "v", CURRENT_VERSION);
+
+ for (int uidIndex = 0; uidIndex < uidStates.size(); uidIndex++) {
+ AppOpsService.UidState uidState = uidStates.valueAt(uidIndex);
+ int uid = uidState.uid;
+
+ for (int pkgIndex = 0; pkgIndex < uidState.pkgOps.size(); pkgIndex++) {
+ String packageName = uidState.pkgOps.keyAt(pkgIndex);
+ AppOpsService.Ops ops = uidState.pkgOps.valueAt(pkgIndex);
+
+ out.startTag(null, TAG_PACKAGE);
+ out.attribute(null, ATTR_NAME, packageName);
+ out.startTag(null, TAG_UID);
+ out.attributeInt(null, ATTR_NAME, uid);
+
+ for (int opIndex = 0; opIndex < ops.size(); opIndex++) {
+ AppOpsService.Op op = ops.valueAt(opIndex);
+
+ out.startTag(null, TAG_OP);
+ out.attributeInt(null, ATTR_NAME, op.op);
+
+ writeDeviceAttributedOps(out, op);
+
+ out.endTag(null, TAG_OP);
+ }
+
+ out.endTag(null, TAG_UID);
+ out.endTag(null, TAG_PACKAGE);
+ }
+ }
+
+ out.endTag(null, TAG_APP_OPS);
+ out.endDocument();
+ mRecentAccessesFile.finishWrite(stream);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to write state, restoring backup.", e);
+ mRecentAccessesFile.failWrite(stream);
+ }
+ }
+ }
+
+ private void writeDeviceAttributedOps(TypedXmlSerializer out, AppOpsService.Op op)
+ throws IOException {
+ for (String deviceId : op.mDeviceAttributedOps.keySet()) {
+ ArrayMap<String, AttributedOp> attributedOps =
+ op.mDeviceAttributedOps.get(deviceId);
+
+ for (int attrIndex = 0; attrIndex < attributedOps.size(); attrIndex++) {
+ String attributionTag = attributedOps.keyAt(attrIndex);
+ AppOpsManager.AttributedOpEntry attributedOpEntry =
+ attributedOps.valueAt(attrIndex).createAttributedOpEntryLocked();
+
+ final ArraySet<Long> keys = attributedOpEntry.collectKeys();
+ for (int k = 0; k < keys.size(); k++) {
+ final long key = keys.valueAt(k);
+
+ final int uidState = AppOpsManager.extractUidStateFromKey(key);
+ final int flags = AppOpsManager.extractFlagsFromKey(key);
+
+ final long accessTime =
+ attributedOpEntry.getLastAccessTime(uidState, uidState, flags);
+ final long rejectTime =
+ attributedOpEntry.getLastRejectTime(uidState, uidState, flags);
+ final long accessDuration =
+ attributedOpEntry.getLastDuration(uidState, uidState, flags);
+
+ // Proxy information for rejections is not backed up
+ final AppOpsManager.OpEventProxyInfo proxy =
+ attributedOpEntry.getLastProxyInfo(uidState, uidState, flags);
+
+ if (accessTime <= 0 && rejectTime <= 0 && accessDuration <= 0
+ && proxy == null) {
+ continue;
+ }
+
+ out.startTag(null, TAG_ATTRIBUTION_OP);
+ if (attributionTag != null) {
+ out.attribute(null, ATTR_ID, attributionTag);
+ }
+ out.attributeLong(null, ATTR_NAME, key);
+
+ if (!Objects.equals(
+ deviceId, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) {
+ out.attribute(null, ATTR_DEVICE_ID, deviceId);
+ }
+ if (accessTime > 0) {
+ out.attributeLong(null, ATTR_ACCESS_TIME, accessTime);
+ }
+ if (rejectTime > 0) {
+ out.attributeLong(null, ATTR_REJECT_TIME, rejectTime);
+ }
+ if (accessDuration > 0) {
+ out.attributeLong(null, ATTR_ACCESS_DURATION, accessDuration);
+ }
+ if (proxy != null) {
+ out.attributeInt(null, ATTR_PROXY_UID, proxy.getUid());
+
+ if (proxy.getPackageName() != null) {
+ out.attribute(null, ATTR_PROXY_PACKAGE, proxy.getPackageName());
+ }
+ if (proxy.getAttributionTag() != null) {
+ out.attribute(
+ null, ATTR_PROXY_ATTRIBUTION_TAG, proxy.getAttributionTag());
+ }
+ if (proxy.getDeviceId() != null
+ && !Objects.equals(
+ proxy.getDeviceId(),
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) {
+ out.attribute(null, ATTR_PROXY_DEVICE_ID, proxy.getDeviceId());
+ }
+ }
+
+ out.endTag(null, TAG_ATTRIBUTION_OP);
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 1acd300..1bb7922 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -71,6 +71,7 @@
import static android.content.Intent.EXTRA_REPLACING;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
+import static android.permission.flags.Flags.deviceAwareAppOpNewSchemaEnabled;
import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
@@ -261,6 +262,7 @@
private final @Nullable File mNoteOpCallerStacktracesFile;
final Handler mHandler;
+ private final AppOpsRecentAccessPersistence mRecentAccessPersistence;
/**
* Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new
* objects
@@ -408,7 +410,7 @@
private @Nullable UserManagerInternal mUserManagerInternal;
/** Interface for app-op modes.*/
- @VisibleForTesting
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
AppOpsCheckingServiceInterface mAppOpsCheckingService;
/** Interface for app-op restrictions.*/
@@ -528,7 +530,7 @@
@VisibleForTesting
final Constants mConstants;
- @VisibleForTesting
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
final class UidState {
public final int uid;
@@ -642,7 +644,7 @@
}
}
- private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent,
+ @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent,
@Nullable String attributionTag, String persistentDeviceId) {
ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.get(
persistentDeviceId);
@@ -1003,6 +1005,7 @@
LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
mStorageFile = new AtomicFile(storageFile, "appops_legacy");
mRecentAccessesFile = new AtomicFile(recentAccessesFile, "appops_accesses");
+ mRecentAccessPersistence = new AppOpsRecentAccessPersistence(mRecentAccessesFile, this);
if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED) {
mNoteOpCallerStacktracesFile = new File(SystemServiceManager.ensureSystemDir(),
@@ -4910,7 +4913,13 @@
if (!mRecentAccessesFile.exists()) {
readRecentAccesses(mStorageFile);
} else {
- readRecentAccesses(mRecentAccessesFile);
+ if (deviceAwareAppOpNewSchemaEnabled()) {
+ synchronized (this) {
+ mRecentAccessPersistence.readRecentAccesses(mUidStates);
+ }
+ } else {
+ readRecentAccesses(mRecentAccessesFile);
+ }
}
}
@@ -5091,6 +5100,14 @@
@VisibleForTesting
void writeRecentAccesses() {
+ if (deviceAwareAppOpNewSchemaEnabled()) {
+ synchronized (this) {
+ mRecentAccessPersistence.writeRecentAccesses(mUidStates);
+ }
+ mHistoricalRegistry.writeAndClearDiscreteHistory();
+ return;
+ }
+
synchronized (mRecentAccessesFile) {
FileOutputStream stream;
try {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index c9612ca..e0790da 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -293,6 +293,7 @@
Iterator<Entry<Pair<Integer, String>, AdiDeviceState>> iterator =
mDeviceInventory.entrySet().iterator();
if (iterator.hasNext()) {
+ iterator.next();
iterator.remove();
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 7deef2f..72c5254 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2123,7 +2123,10 @@
return AudioProductStrategy.getAudioProductStrategies();
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ @android.annotation.EnforcePermission(anyOf = {
+ MODIFY_AUDIO_SETTINGS_PRIVILEGED,
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING
+ })
/**
* @return the List of {@link android.media.audiopolicy.AudioVolumeGroup} discovered from the
* platform configuration file.
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 30d12e6..1949e6f 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -56,10 +56,12 @@
import com.android.server.display.brightness.BrightnessEvent;
import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.config.HysteresisLevels;
+import com.android.server.display.feature.DisplayManagerFlags;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.TimeUnit;
/**
* Manages the associated display brightness when in auto-brightness mode. This is also
@@ -206,7 +208,7 @@
private float mScreenBrighteningThreshold;
private float mScreenDarkeningThreshold;
// The most recent light sample.
- private float mLastObservedLux = INVALID_LUX;
+ private float mLastObservedLux;
// The time of the most light recent sample.
private long mLastObservedLuxTime;
@@ -277,6 +279,8 @@
private Clock mClock;
private final Injector mInjector;
+ private final DisplayManagerFlags mDisplayManagerFlags;
+
AutomaticBrightnessController(Callbacks callbacks, Looper looper,
SensorManager sensorManager, Sensor lightSensor,
SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap,
@@ -291,7 +295,8 @@
BrightnessRangeController brightnessModeController,
BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
int ambientLightHorizonLong, float userLux, float userNits,
- BrightnessClamperController brightnessClamperController) {
+ BrightnessClamperController brightnessClamperController,
+ DisplayManagerFlags displayManagerFlags) {
this(new Injector(), callbacks, looper, sensorManager, lightSensor,
brightnessMappingStrategyMap, lightSensorWarmUpTime, brightnessMin, brightnessMax,
dozeScaleFactor, lightSensorRate, initialLightSensorRate,
@@ -301,7 +306,7 @@
screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
screenBrightnessThresholdsIdle, context, brightnessModeController,
brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux,
- userNits, brightnessClamperController
+ userNits, brightnessClamperController, displayManagerFlags
);
}
@@ -320,9 +325,10 @@
BrightnessRangeController brightnessRangeController,
BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
int ambientLightHorizonLong, float userLux, float userNits,
- BrightnessClamperController brightnessClamperController) {
+ BrightnessClamperController brightnessClamperController,
+ DisplayManagerFlags displayManagerFlags) {
mInjector = injector;
- mClock = injector.createClock();
+ mClock = injector.createClock(displayManagerFlags.offloadControlsDozeAutoBrightness());
mContext = context;
mCallbacks = callbacks;
mSensorManager = sensorManager;
@@ -367,6 +373,7 @@
mBrightnessClamperController = brightnessClamperController;
mBrightnessThrottler = brightnessThrottler;
mBrightnessMappingStrategyMap = brightnessMappingStrategyMap;
+ mDisplayManagerFlags = displayManagerFlags;
// Use the given short-term model
if (userNits != BrightnessMappingStrategy.INVALID_NITS) {
@@ -429,34 +436,6 @@
return mRawScreenAutoBrightness;
}
- /**
- * Get the automatic screen brightness based on the last observed lux reading. Used e.g. when
- * entering doze - we disable the light sensor, invalidate the lux, but we still need to set
- * the initial brightness in doze mode.
- */
- public float getAutomaticScreenBrightnessBasedOnLastUsedLux(
- BrightnessEvent brightnessEvent) {
- float lastUsedLux = mAmbientLux;
- if (lastUsedLux == INVALID_LUX) {
- return PowerManager.BRIGHTNESS_INVALID_FLOAT;
- }
-
- float brightness = mCurrentBrightnessMapper.getBrightness(lastUsedLux,
- mForegroundAppPackageName, mForegroundAppCategory);
- if (shouldApplyDozeScaleFactor()) {
- brightness *= mDozeScaleFactor;
- }
-
- if (brightnessEvent != null) {
- brightnessEvent.setLux(lastUsedLux);
- brightnessEvent.setRecommendedBrightness(brightness);
- brightnessEvent.setFlags(brightnessEvent.getFlags()
- | (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
- brightnessEvent.setAutoBrightnessMode(getMode());
- }
- return brightness;
- }
-
public boolean hasValidAmbientLux() {
return mAmbientLuxValid;
}
@@ -747,7 +726,6 @@
mRecentLightSamples++;
mAmbientLightRingBuffer.prune(time - mAmbientLightHorizonLong);
mAmbientLightRingBuffer.push(time, lux);
-
// Remember this sample value.
mLastObservedLux = lux;
mLastObservedLuxTime = time;
@@ -891,7 +869,7 @@
}
private void updateAmbientLux() {
- long time = mClock.uptimeMillis();
+ long time = mClock.getSensorEventScaleTime();
mAmbientLightRingBuffer.prune(time - mAmbientLightHorizonLong);
updateAmbientLux(time);
}
@@ -968,7 +946,16 @@
Slog.d(TAG, "updateAmbientLux: Scheduling ambient lux update for " +
nextTransitionTime + TimeUtils.formatUptime(nextTransitionTime));
}
- mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX, nextTransitionTime);
+
+ // The nextTransitionTime is computed as elapsedTime(Which also accounts for the time when
+ // android was sleeping) as the main reference. However, handlers work on the uptime(Not
+ // accounting for the time when android was sleeping)
+ mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX,
+ convertToUptime(nextTransitionTime));
+ }
+
+ private long convertToUptime(long time) {
+ return time - mClock.getSensorEventScaleTime() + mClock.uptimeMillis();
}
private void updateAutoBrightness(boolean sendUpdate, boolean isManuallySet) {
@@ -1185,15 +1172,13 @@
}
mPausedShortTermModel.copyFrom(tempShortTermModel);
}
-
- update();
}
/**
* Responsible for switching the AutomaticBrightnessMode of the associated display. Also takes
* care of resetting the short term model wherever required
*/
- public void switchMode(@AutomaticBrightnessMode int mode) {
+ public void switchMode(@AutomaticBrightnessMode int mode, boolean sendUpdate) {
if (!mBrightnessMappingStrategyMap.contains(mode)) {
return;
}
@@ -1208,6 +1193,11 @@
resetShortTermModel();
mCurrentBrightnessMapper = mBrightnessMappingStrategyMap.get(mode);
}
+ if (sendUpdate) {
+ update();
+ } else {
+ updateAutoBrightness(/* sendUpdate= */ false, /* isManuallySet= */ false);
+ }
}
float getUserLux() {
@@ -1391,7 +1381,9 @@
@Override
public void onSensorChanged(SensorEvent event) {
if (mLightSensorEnabled) {
- final long time = mClock.uptimeMillis();
+ // The time received from the sensor is in nano seconds, hence changing it to ms
+ final long time = (mDisplayManagerFlags.offloadControlsDozeAutoBrightness())
+ ? TimeUnit.NANOSECONDS.toMillis(event.timestamp) : mClock.uptimeMillis();
final float lux = event.values[0];
handleLightSensorEvent(time, lux);
}
@@ -1424,6 +1416,12 @@
* Returns current time in milliseconds since boot, not counting time spent in deep sleep.
*/
long uptimeMillis();
+
+ /**
+ * Gets the time on either the elapsedTime or the uptime scale, depending on how we
+ * processing the events from the sensor
+ */
+ long getSensorEventScaleTime();
}
/**
@@ -1571,7 +1569,8 @@
StringBuilder buf = new StringBuilder();
buf.append('[');
for (int i = 0; i < mCount; i++) {
- final long next = i + 1 < mCount ? getTime(i + 1) : mClock.uptimeMillis();
+ final long next = i + 1 < mCount ? getTime(i + 1)
+ : mClock.getSensorEventScaleTime();
if (i != 0) {
buf.append(", ");
}
@@ -1596,13 +1595,31 @@
}
}
+ private static class RealClock implements Clock {
+ private final boolean mOffloadControlsDozeBrightness;
+
+ RealClock(boolean offloadControlsDozeBrightness) {
+ mOffloadControlsDozeBrightness = offloadControlsDozeBrightness;
+ }
+
+ @Override
+ public long uptimeMillis() {
+ return SystemClock.uptimeMillis();
+ }
+
+ public long getSensorEventScaleTime() {
+ return (mOffloadControlsDozeBrightness)
+ ? SystemClock.elapsedRealtime() : uptimeMillis();
+ }
+ }
+
public static class Injector {
public Handler getBackgroundThreadHandler() {
return BackgroundThread.getHandler();
}
- Clock createClock() {
- return SystemClock::uptimeMillis;
+ Clock createClock(boolean offloadControlsDozeBrightness) {
+ return new RealClock(offloadControlsDozeBrightness);
}
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 27ea1cd..d4c0b01 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1745,6 +1745,7 @@
if (projection != null) {
IBinder taskWindowContainerToken = projection.getLaunchCookie() == null ? null
: projection.getLaunchCookie().binder;
+ int taskId = projection.getTaskId();
if (taskWindowContainerToken == null) {
// Record a particular display.
session = ContentRecordingSession.createDisplaySession(
@@ -1752,7 +1753,7 @@
} else {
// Record a single task indicated by the launch cookie.
session = ContentRecordingSession.createTaskSession(
- taskWindowContainerToken);
+ taskWindowContainerToken, taskId);
}
}
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index 65c9f35..f77a360 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -52,6 +52,14 @@
}
@Override
+ public boolean allowAutoBrightnessInDoze() {
+ if (mDisplayOffloader == null) {
+ return false;
+ }
+ return mDisplayOffloader.allowAutoBrightnessInDoze();
+ }
+
+ @Override
public void updateBrightness(float brightness) {
if (mIsActive) {
mDisplayPowerController.setBrightnessFromOffload(brightness);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 0fcdf19..7d482f7 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1115,7 +1115,7 @@
screenBrightnessThresholdsIdle, mContext, mBrightnessRangeController,
mBrightnessThrottler, mDisplayDeviceConfig.getAmbientHorizonShort(),
mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userNits,
- mBrightnessClamperController);
+ mBrightnessClamperController, mFlags);
mDisplayBrightnessController.setUpAutoBrightness(
mAutomaticBrightnessController, mSensorManager, mDisplayDeviceConfig, mHandler,
defaultModeBrightnessMapper, mIsEnabled, mLeadDisplayId);
@@ -1185,7 +1185,8 @@
@AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
boolean isIdle = mode == AUTO_BRIGHTNESS_MODE_IDLE;
if (mAutomaticBrightnessController != null) {
- mAutomaticBrightnessController.switchMode(mode);
+ // Set sendUpdate to true to make sure that updatePowerState() gets called
+ mAutomaticBrightnessController.switchMode(mode, /* sendUpdate= */ true);
setAnimatorRampSpeeds(isIdle);
}
Message msg = mHandler.obtainMessage();
@@ -1334,7 +1335,6 @@
mDisplayStateController.shouldPerformScreenOffTransition());
state = mPowerState.getScreenState();
-
DisplayBrightnessState displayBrightnessState = mDisplayBrightnessController
.updateBrightness(mPowerRequest, state);
float brightnessState = displayBrightnessState.getBrightness();
@@ -1366,17 +1366,26 @@
// request changes.
final boolean wasShortTermModelActive =
mAutomaticBrightnessStrategy.isShortTermModelActive();
+ boolean allowAutoBrightnessWhileDozing =
+ mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig();
+ if (mFlags.offloadControlsDozeAutoBrightness() && mFlags.isDisplayOffloadEnabled()
+ && mDisplayOffloadSession != null) {
+ allowAutoBrightnessWhileDozing &= mDisplayOffloadSession.allowAutoBrightnessInDoze();
+ }
if (!mFlags.isRefactorDisplayPowerControllerEnabled()) {
// Switch to doze auto-brightness mode if needed
if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null
&& !mAutomaticBrightnessController.isInIdleMode()) {
+ // Set sendUpdate to false, we're already in updatePowerState() so there's no need
+ // to trigger it again
mAutomaticBrightnessController.switchMode(Display.isDozeState(state)
- ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
+ ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT,
+ /* sendUpdate= */ false);
}
mAutomaticBrightnessStrategy.setAutoBrightnessState(state,
- mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig(),
- mBrightnessReasonTemp.getReason(), mPowerRequest.policy,
+ allowAutoBrightnessWhileDozing, mBrightnessReasonTemp.getReason(),
+ mPowerRequest.policy,
mDisplayBrightnessController.getLastUserSetScreenBrightness(),
userSetBrightnessChanged);
}
@@ -1443,47 +1452,27 @@
}
if (Display.isDozeState(state)) {
- // If there's an offload session, we need to set the initial doze brightness before
- // the offload session starts controlling the brightness.
- // During the transition DOZE_SUSPEND -> DOZE -> DOZE_SUSPEND, this brightness strategy
- // will be selected again, meaning that no new brightness will be sent to the hardware
- // and the display will stay at the brightness level set by the offload session.
+ // TODO(b/329676661): Introduce a config property to choose between this brightness
+ // strategy and DOZE_DEFAULT
+ // On some devices, when auto-brightness is disabled and the device is dozing, we use
+ // the current brightness setting scaled by the doze scale factor
if ((Float.isNaN(brightnessState)
|| displayBrightnessState.getDisplayBrightnessStrategyName()
.equals(DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME))
&& mFlags.isDisplayOffloadEnabled()
- && mDisplayOffloadSession != null) {
- if (mAutomaticBrightnessController != null
- && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
- // Use the auto-brightness curve and the last observed lux
- rawBrightnessState = mAutomaticBrightnessController
- .getAutomaticScreenBrightnessBasedOnLastUsedLux(
- mTempBrightnessEvent);
- } else {
- rawBrightnessState = getDozeBrightnessForOffload();
- mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
- | BrightnessEvent.FLAG_DOZE_SCALE);
- }
-
- if (BrightnessUtils.isValidBrightnessValue(rawBrightnessState)) {
- brightnessState = clampScreenBrightness(rawBrightnessState);
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_INITIAL);
-
- if (mAutomaticBrightnessController != null
- && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
- // Keep the brightness in the setting so that we can use it after the screen
- // turns on, until a lux sample becomes available. We don't do this when
- // auto-brightness is disabled - in that situation we still want to use
- // the last brightness from when the screen was on.
- updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
- }
- }
+ && mDisplayOffloadSession != null
+ && (mAutomaticBrightnessController == null
+ || !mAutomaticBrightnessStrategy.shouldUseAutoBrightness())) {
+ rawBrightnessState = getDozeBrightnessForOffload();
+ brightnessState = clampScreenBrightness(rawBrightnessState);
+ mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_MANUAL);
+ mTempBrightnessEvent.setFlags(
+ mTempBrightnessEvent.getFlags() | BrightnessEvent.FLAG_DOZE_SCALE);
}
// Use default brightness when dozing unless overridden.
- if (Float.isNaN(brightnessState)
- || displayBrightnessState.getDisplayBrightnessStrategyName()
- .equals(DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME)) {
+ if (Float.isNaN(brightnessState) && Display.isDozeState(state)
+ && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()) {
rawBrightnessState = mScreenBrightnessDozeConfig;
brightnessState = clampScreenBrightness(rawBrightnessState);
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
@@ -3169,7 +3158,8 @@
BrightnessRangeController brightnessModeController,
BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
int ambientLightHorizonLong, float userLux, float userNits,
- BrightnessClamperController brightnessClamperController) {
+ BrightnessClamperController brightnessClamperController,
+ DisplayManagerFlags displayManagerFlags) {
return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor,
brightnessMappingStrategyMap, lightSensorWarmUpTime, brightnessMin,
@@ -3180,7 +3170,7 @@
screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
screenBrightnessThresholdsIdle, context, brightnessModeController,
brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux,
- userNits, brightnessClamperController);
+ userNits, brightnessClamperController, displayManagerFlags);
}
BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index b24caf4..44c8d1c 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -136,6 +136,9 @@
handleExternalDisplayConnectedLocked(logicalDisplay);
}
}
+ if (!mDisplayIdsWaitingForBootCompletion.isEmpty()) {
+ mLogicalDisplayMapper.updateLogicalDisplaysLocked();
+ }
mDisplayIdsWaitingForBootCompletion.clear();
}
@@ -222,7 +225,7 @@
} else {
// As external display is enabled by default, need to disable it now.
// TODO(b/292196201) Remove when the display can be disabled before DPC is created.
- mLogicalDisplayMapper.setDisplayEnabledLocked(logicalDisplay, false);
+ mLogicalDisplayMapper.setEnabledLocked(logicalDisplay, false);
}
if (!isExternalDisplayAllowed()) {
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 01485cb..e645e98 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -1195,7 +1195,6 @@
return display;
}
- @VisibleForTesting
void setEnabledLocked(LogicalDisplay display, boolean isEnabled) {
final int displayId = display.getDisplayIdLocked();
final DisplayInfo info = display.getDisplayInfoLocked();
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessReason.java b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
index fc95d15..9bf10a7 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessReason.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
@@ -40,8 +40,8 @@
public static final int REASON_SCREEN_OFF_BRIGHTNESS_SENSOR = 9;
public static final int REASON_FOLLOWER = 10;
public static final int REASON_OFFLOAD = 11;
- public static final int REASON_DOZE_INITIAL = 12;
- public static final int REASON_MAX = REASON_DOZE_INITIAL;
+ public static final int REASON_DOZE_MANUAL = 12;
+ public static final int REASON_MAX = REASON_DOZE_MANUAL;
public static final int MODIFIER_DIMMED = 0x1;
public static final int MODIFIER_LOW_POWER = 0x2;
@@ -208,8 +208,8 @@
return "follower";
case REASON_OFFLOAD:
return "offload";
- case REASON_DOZE_INITIAL:
- return "doze_initial";
+ case REASON_DOZE_MANUAL:
+ return "doze_manual";
default:
return Integer.toString(reason);
}
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 37b6931..2907364 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
@@ -15,8 +15,6 @@
*/
package com.android.server.display.brightness.strategy;
-import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
-
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
@@ -129,9 +127,11 @@
public void setAutoBrightnessState(int targetDisplayState,
boolean allowAutoBrightnessWhileDozingConfig, int brightnessReason, int policy,
float lastUserSetScreenBrightness, boolean userSetBrightnessChanged) {
- switchMode(targetDisplayState);
+ // We are still in the process of updating the power state, so there's no need to trigger
+ // an update again
+ switchMode(targetDisplayState, /* sendUpdate= */ false);
final boolean autoBrightnessEnabledInDoze =
- allowAutoBrightnessWhileDozingConfig && policy == POLICY_DOZE;
+ allowAutoBrightnessWhileDozingConfig && Display.isDozeState(targetDisplayState);
mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
&& (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze)
&& brightnessReason != BrightnessReason.REASON_OVERRIDE
@@ -371,23 +371,6 @@
}
/**
- * Get the automatic screen brightness based on the last observed lux reading. Used e.g. when
- * entering doze - we disable the light sensor, invalidate the lux, but we still need to set
- * the initial brightness in doze mode.
- * @param brightnessEvent Event object to populate with details about why the specific
- * brightness was chosen.
- */
- public float getAutomaticScreenBrightnessBasedOnLastUsedLux(
- BrightnessEvent brightnessEvent) {
- float brightness = (mAutomaticBrightnessController != null)
- ? mAutomaticBrightnessController
- .getAutomaticScreenBrightnessBasedOnLastUsedLux(brightnessEvent)
- : PowerManager.BRIGHTNESS_INVALID_FLOAT;
- adjustAutomaticBrightnessStateIfValid(brightness);
- return brightness;
- }
-
- /**
* Returns if the auto brightness has been applied
*/
public boolean hasAppliedAutoBrightness() {
@@ -495,14 +478,12 @@
mIsShortTermModelActive = mAutomaticBrightnessController.hasUserDataPoints();
}
}
-
-
- private void switchMode(int state) {
+ private void switchMode(int state, boolean sendUpdate) {
if (mDisplayManagerFlags.areAutoBrightnessModesEnabled()
&& mAutomaticBrightnessController != null
&& !mAutomaticBrightnessController.isInIdleMode()) {
mAutomaticBrightnessController.switchMode(Display.isDozeState(state)
- ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
+ ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT, sendUpdate);
}
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java
index 58670c9..4d9c18a 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java
@@ -15,8 +15,6 @@
*/
package com.android.server.display.brightness.strategy;
-import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
-
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.display.BrightnessConfiguration;
@@ -110,7 +108,7 @@
boolean allowAutoBrightnessWhileDozingConfig, int brightnessReason, int policy,
float lastUserSetScreenBrightness, boolean userSetBrightnessChanged) {
final boolean autoBrightnessEnabledInDoze =
- allowAutoBrightnessWhileDozingConfig && policy == POLICY_DOZE;
+ allowAutoBrightnessWhileDozingConfig && Display.isDozeState(targetDisplayState);
mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
&& (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze)
&& brightnessReason != BrightnessReason.REASON_OVERRIDE
@@ -273,23 +271,6 @@
}
/**
- * Get the automatic screen brightness based on the last observed lux reading. Used e.g. when
- * entering doze - we disable the light sensor, invalidate the lux, but we still need to set
- * the initial brightness in doze mode.
- * @param brightnessEvent Event object to populate with details about why the specific
- * brightness was chosen.
- */
- public float getAutomaticScreenBrightnessBasedOnLastUsedLux(
- BrightnessEvent brightnessEvent) {
- float brightness = (mAutomaticBrightnessController != null)
- ? mAutomaticBrightnessController
- .getAutomaticScreenBrightnessBasedOnLastUsedLux(brightnessEvent)
- : PowerManager.BRIGHTNESS_INVALID_FLOAT;
- adjustAutomaticBrightnessStateIfValid(brightness);
- return brightness;
- }
-
- /**
* Gets the auto-brightness adjustment flag change reason
*/
public int getAutoBrightnessAdjustmentReasonsFlags() {
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 a5414fc..8f775a5 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -154,6 +154,11 @@
Flags::useFusionProxSensor
);
+ private final FlagState mOffloadControlsDozeAutoBrightness = new FlagState(
+ Flags.FLAG_OFFLOAD_CONTROLS_DOZE_AUTO_BRIGHTNESS,
+ Flags::offloadControlsDozeAutoBrightness
+ );
+
private final FlagState mPeakRefreshRatePhysicalLimit = new FlagState(
Flags.FLAG_ENABLE_PEAK_REFRESH_RATE_PHYSICAL_LIMIT,
Flags::enablePeakRefreshRatePhysicalLimit
@@ -327,6 +332,13 @@
return mUseFusionProxSensor.getName();
}
+ /**
+ * @return Whether DisplayOffload should control auto-brightness in doze
+ */
+ public boolean offloadControlsDozeAutoBrightness() {
+ return mOffloadControlsDozeAutoBrightness.isEnabled();
+ }
+
public boolean isPeakRefreshRatePhysicalLimitEnabled() {
return mPeakRefreshRatePhysicalLimit.isEnabled();
}
@@ -373,6 +385,7 @@
pw.println(" " + mRefactorDisplayPowerController);
pw.println(" " + mResolutionBackupRestore);
pw.println(" " + mUseFusionProxSensor);
+ pw.println(" " + mOffloadControlsDozeAutoBrightness);
pw.println(" " + mPeakRefreshRatePhysicalLimit);
pw.println(" " + mIgnoreAppPreferredRefreshRate);
pw.println(" " + mSynthetic60hzModes);
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 316b6db..697218d 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -246,6 +246,17 @@
}
flag {
+ name: "offload_controls_doze_auto_brightness"
+ namespace: "display_manager"
+ description: "Allows the registered DisplayOffloader to control if auto-brightness is used in doze"
+ bug: "327392714"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_peak_refresh_rate_physical_limit"
namespace: "display_manager"
description: "Flag for adding physical refresh rate limit if smooth display setting is on "
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 e20ac73..76a2827 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -1116,6 +1116,9 @@
if (mIsLowPower) {
for (int i = 0; i < mDisplayDeviceConfigByDisplay.size(); i++) {
DisplayDeviceConfig config = mDisplayDeviceConfigByDisplay.valueAt(i);
+ if (config == null) {
+ continue;
+ }
List<SupportedModeData> supportedModes = config
.getRefreshRateData().lowPowerSupportedModes;
mVotesStorage.updateVote(
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
index f992a23..88da6fb 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
@@ -100,6 +100,9 @@
// Map from port ID to HdmiDeviceInfo.
private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap;
+ // Cached physical address.
+ private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
+
HdmiCecNetwork(HdmiControlService hdmiControlService,
HdmiCecController hdmiCecController,
HdmiMhlControllerStub hdmiMhlController) {
@@ -431,6 +434,8 @@
// each port. Return empty array if CEC HAL didn't provide the info.
if (mHdmiCecController != null) {
cecPortInfo = mHdmiCecController.getPortInfos();
+ // Invalid cached physical address.
+ mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
}
if (cecPortInfo == null) {
return;
@@ -856,7 +861,10 @@
}
public int getPhysicalAddress() {
- return mHdmiCecController.getPhysicalAddress();
+ if (mPhysicalAddress == Constants.INVALID_PHYSICAL_ADDRESS) {
+ mPhysicalAddress = mHdmiCecController.getPhysicalAddress();
+ }
+ return mPhysicalAddress;
}
@ServiceThreadOnly
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index cca73b5..dbd1e65 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1567,7 +1567,7 @@
* Returns physical address of the device.
*/
int getPhysicalAddress() {
- return mCecController.getPhysicalAddress();
+ return mHdmiCecNetwork.getPhysicalAddress();
}
/**
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java
index 4ee2e99..6da7a65 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java
@@ -172,17 +172,22 @@
@Override
public String toString() {
- String out = ContextHubTransaction.typeToString(mTransactionType, true /* upperCase */)
- + " (";
+ StringBuilder out = new StringBuilder();
+ out.append(ContextHubTransaction.typeToString(mTransactionType,
+ /* upperCase= */ true));
+ out.append(" (");
if (mNanoAppId != null) {
- out += "appId = 0x" + Long.toHexString(mNanoAppId) + ", ";
+ out.append("appId = 0x");
+ out.append(Long.toHexString(mNanoAppId));
+ out.append(", ");
}
- out += "package = " + mPackage;
+ out.append("package = ");
+ out.append(mPackage);
if (mMessageSequenceNumber != null) {
- out += ", messageSequenceNumber = " + mMessageSequenceNumber;
+ out.append(", messageSequenceNumber = ");
+ out.append(mMessageSequenceNumber);
}
- out += ")";
-
- return out;
+ out.append(")");
+ return out.toString();
}
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 39df5be..286e789 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -1181,7 +1181,7 @@
GnssPsdsDownloader.LONG_TERM_PSDS_SERVER_INDEX));
}
} else if ("request_power_stats".equals(command)) {
- mGnssNative.requestPowerStats();
+ mGnssNative.requestPowerStats(Runnable::run, powerStats -> {});
} else {
Log.w(TAG, "sendExtraCommand: unknown command " + command);
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
index 133704d..6a72cc7 100644
--- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java
+++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
@@ -314,9 +314,9 @@
ipw.decreaseIndent();
}
- GnssPowerStats powerStats = mGnssNative.getPowerStats();
+ GnssPowerStats powerStats = mGnssNative.getLastKnownPowerStats();
if (powerStats != null) {
- ipw.println("Last Power Stats:");
+ ipw.println("Last Known Power Stats:");
ipw.increaseIndent();
powerStats.dump(fd, ipw, args, mGnssNative.getCapabilities());
ipw.decreaseIndent();
diff --git a/services/core/java/com/android/server/location/gnss/GnssMetrics.java b/services/core/java/com/android/server/location/gnss/GnssMetrics.java
index dbc903d..ae79b01 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMetrics.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMetrics.java
@@ -16,6 +16,7 @@
package com.android.server.location.gnss;
+import android.annotation.NonNull;
import android.app.StatsManager;
import android.content.Context;
import android.location.GnssSignalQuality;
@@ -60,7 +61,6 @@
private static final double L5_CARRIER_FREQ_RANGE_LOW_HZ = 1164 * 1e6;
private static final double L5_CARRIER_FREQ_RANGE_HIGH_HZ = 1189 * 1e6;
-
private long mLogStartInElapsedRealtimeMs;
GnssPowerMetrics mGnssPowerMetrics;
@@ -608,64 +608,72 @@
}
@Override
- public int onPullAtom(int atomTag, List<StatsEvent> data) {
- if (atomTag == FrameworkStatsLog.GNSS_STATS) {
- data.add(FrameworkStatsLog.buildStatsEvent(atomTag,
- mLocationFailureReportsStatistics.getCount(),
- mLocationFailureReportsStatistics.getLongSum(),
- mTimeToFirstFixMilliSReportsStatistics.getCount(),
- mTimeToFirstFixMilliSReportsStatistics.getLongSum(),
- mPositionAccuracyMetersReportsStatistics.getCount(),
- mPositionAccuracyMetersReportsStatistics.getLongSum(),
- mTopFourAverageCn0DbmHzReportsStatistics.getCount(),
- mTopFourAverageCn0DbmHzReportsStatistics.getLongSum(),
- mL5TopFourAverageCn0DbmHzReportsStatistics.getCount(),
- mL5TopFourAverageCn0DbmHzReportsStatistics.getLongSum(), mSvStatusReports,
- mSvStatusReportsUsedInFix, mL5SvStatusReports,
- mL5SvStatusReportsUsedInFix));
- } else if (atomTag == FrameworkStatsLog.GNSS_POWER_STATS) {
- mGnssNative.requestPowerStats();
- GnssPowerStats gnssPowerStats = mGnssNative.getPowerStats();
- if (gnssPowerStats == null) {
- return StatsManager.PULL_SKIP;
- }
- double[] otherModesEnergyMilliJoule = new double[VENDOR_SPECIFIC_POWER_MODES_SIZE];
- double[] tempGnssPowerStatsOtherModes =
- gnssPowerStats.getOtherModesEnergyMilliJoule();
- if (tempGnssPowerStatsOtherModes.length < VENDOR_SPECIFIC_POWER_MODES_SIZE) {
- System.arraycopy(tempGnssPowerStatsOtherModes, 0,
- otherModesEnergyMilliJoule, 0,
- tempGnssPowerStatsOtherModes.length);
- } else {
- System.arraycopy(tempGnssPowerStatsOtherModes, 0,
- otherModesEnergyMilliJoule, 0,
- VENDOR_SPECIFIC_POWER_MODES_SIZE);
- }
- data.add(FrameworkStatsLog.buildStatsEvent(atomTag,
- (long) (gnssPowerStats.getElapsedRealtimeUncertaintyNanos()),
- (long) (gnssPowerStats.getTotalEnergyMilliJoule() * CONVERT_MILLI_TO_MICRO),
- (long) (gnssPowerStats.getSinglebandTrackingModeEnergyMilliJoule()
- * CONVERT_MILLI_TO_MICRO),
- (long) (gnssPowerStats.getMultibandTrackingModeEnergyMilliJoule()
- * CONVERT_MILLI_TO_MICRO),
- (long) (gnssPowerStats.getSinglebandAcquisitionModeEnergyMilliJoule()
- * CONVERT_MILLI_TO_MICRO),
- (long) (gnssPowerStats.getMultibandAcquisitionModeEnergyMilliJoule()
- * CONVERT_MILLI_TO_MICRO),
- (long) (otherModesEnergyMilliJoule[0] * CONVERT_MILLI_TO_MICRO),
- (long) (otherModesEnergyMilliJoule[1] * CONVERT_MILLI_TO_MICRO),
- (long) (otherModesEnergyMilliJoule[2] * CONVERT_MILLI_TO_MICRO),
- (long) (otherModesEnergyMilliJoule[3] * CONVERT_MILLI_TO_MICRO),
- (long) (otherModesEnergyMilliJoule[4] * CONVERT_MILLI_TO_MICRO),
- (long) (otherModesEnergyMilliJoule[5] * CONVERT_MILLI_TO_MICRO),
- (long) (otherModesEnergyMilliJoule[6] * CONVERT_MILLI_TO_MICRO),
- (long) (otherModesEnergyMilliJoule[7] * CONVERT_MILLI_TO_MICRO),
- (long) (otherModesEnergyMilliJoule[8] * CONVERT_MILLI_TO_MICRO),
- (long) (otherModesEnergyMilliJoule[9] * CONVERT_MILLI_TO_MICRO)));
- } else {
- throw new UnsupportedOperationException("Unknown tagId = " + atomTag);
+ public int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) {
+ switch (atomTag) {
+ case FrameworkStatsLog.GNSS_STATS:
+ return pullGnssStats(atomTag, data);
+ case FrameworkStatsLog.GNSS_POWER_STATS:
+ return pullGnssPowerStats(atomTag, data);
+ default:
+ throw new UnsupportedOperationException("Unknown tagId = " + atomTag);
}
+ }
+ }
+
+ private int pullGnssStats(int atomTag, List<StatsEvent> data) {
+ data.add(FrameworkStatsLog.buildStatsEvent(atomTag,
+ mLocationFailureReportsStatistics.getCount(),
+ mLocationFailureReportsStatistics.getLongSum(),
+ mTimeToFirstFixMilliSReportsStatistics.getCount(),
+ mTimeToFirstFixMilliSReportsStatistics.getLongSum(),
+ mPositionAccuracyMetersReportsStatistics.getCount(),
+ mPositionAccuracyMetersReportsStatistics.getLongSum(),
+ mTopFourAverageCn0DbmHzReportsStatistics.getCount(),
+ mTopFourAverageCn0DbmHzReportsStatistics.getLongSum(),
+ mL5TopFourAverageCn0DbmHzReportsStatistics.getCount(),
+ mL5TopFourAverageCn0DbmHzReportsStatistics.getLongSum(), mSvStatusReports,
+ mSvStatusReportsUsedInFix, mL5SvStatusReports,
+ mL5SvStatusReportsUsedInFix));
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private int pullGnssPowerStats(int atomTag, List<StatsEvent> data) {
+ GnssPowerStats powerStats = mGnssNative.requestPowerStatsBlocking();
+ if (powerStats == null) {
+ return StatsManager.PULL_SKIP;
+ } else {
+ data.add(createPowerStatsEvent(atomTag, powerStats));
return StatsManager.PULL_SUCCESS;
}
}
+
+ private static StatsEvent createPowerStatsEvent(int atomTag,
+ @NonNull GnssPowerStats powerStats) {
+ double[] otherModesEnergyMilliJoule = new double[VENDOR_SPECIFIC_POWER_MODES_SIZE];
+ double[] tempGnssPowerStatsOtherModes = powerStats.getOtherModesEnergyMilliJoule();
+ System.arraycopy(tempGnssPowerStatsOtherModes, 0,
+ otherModesEnergyMilliJoule, 0,
+ Math.min(tempGnssPowerStatsOtherModes.length, VENDOR_SPECIFIC_POWER_MODES_SIZE));
+ return FrameworkStatsLog.buildStatsEvent(atomTag,
+ (long) (powerStats.getElapsedRealtimeUncertaintyNanos()),
+ (long) (powerStats.getTotalEnergyMilliJoule() * CONVERT_MILLI_TO_MICRO),
+ (long) (powerStats.getSinglebandTrackingModeEnergyMilliJoule()
+ * CONVERT_MILLI_TO_MICRO),
+ (long) (powerStats.getMultibandTrackingModeEnergyMilliJoule()
+ * CONVERT_MILLI_TO_MICRO),
+ (long) (powerStats.getSinglebandAcquisitionModeEnergyMilliJoule()
+ * CONVERT_MILLI_TO_MICRO),
+ (long) (powerStats.getMultibandAcquisitionModeEnergyMilliJoule()
+ * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[0] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[1] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[2] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[3] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[4] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[5] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[6] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[7] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[8] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[9] * CONVERT_MILLI_TO_MICRO));
+ }
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
index bdd4885..c79a21a 100644
--- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
+++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
@@ -18,7 +18,9 @@
import static com.android.server.location.gnss.GnssManagerService.TAG;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.location.GnssAntennaInfo;
import android.location.GnssCapabilities;
@@ -29,6 +31,7 @@
import android.location.GnssStatus;
import android.location.Location;
import android.os.Binder;
+import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
@@ -46,9 +49,13 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
/**
* Entry point for most GNSS HAL commands and callbacks.
@@ -140,6 +147,8 @@
public static final int AGPS_SETID_TYPE_IMSI = 1;
public static final int AGPS_SETID_TYPE_MSISDN = 2;
+ private static final int POWER_STATS_REQUEST_TIMEOUT_MILLIS = 100;
+
@IntDef(prefix = "AGPS_SETID_TYPE_", value = {AGPS_SETID_TYPE_NONE, AGPS_SETID_TYPE_IMSI,
AGPS_SETID_TYPE_MSISDN})
@Retention(RetentionPolicy.SOURCE)
@@ -289,6 +298,15 @@
byte responseType, boolean inEmergencyMode, boolean isCachedLocation);
}
+ /** Callback for reporting {@link GnssPowerStats} */
+ public interface PowerStatsCallback {
+ /**
+ * Called when power stats are reported.
+ * @param powerStats non-null value when power stats are available, {@code null} otherwise.
+ */
+ void onReportPowerStats(@Nullable GnssPowerStats powerStats);
+ }
+
// set lower than the current ITAR limit of 600m/s to allow this to trigger even if GPS HAL
// stops output right at 600m/s, depriving this of the information of a device that reaches
// greater than 600m/s, and higher than the speed of sound to avoid impacting most use cases.
@@ -311,6 +329,8 @@
@GuardedBy("GnssNative.class")
private static GnssNative sInstance;
+ private final Handler mHandler;
+
/**
* Sets GnssHal instance to use for testing.
*/
@@ -367,6 +387,14 @@
private NavigationMessageCallbacks[] mNavigationMessageCallbacks =
new NavigationMessageCallbacks[0];
+ private @Nullable GnssPowerStats mLastKnownPowerStats = null;
+ private final Object mPowerStatsLock = new Object();
+ private final Runnable mPowerStatsTimeoutCallback = () -> {
+ Log.d(TAG, "Request for power stats timed out");
+ reportGnssPowerStats(null);
+ };
+ private final List<PowerStatsCallback> mPendingPowerStatsCallbacks = new ArrayList<>();
+
// these callbacks may only have a single implementation
private GeofenceCallbacks mGeofenceCallbacks;
private TimeCallbacks mTimeCallbacks;
@@ -381,7 +409,6 @@
private GnssCapabilities mCapabilities = new GnssCapabilities.Builder().build();
private @GnssCapabilities.TopHalCapabilityFlags int mTopFlags;
- private @Nullable GnssPowerStats mPowerStats = null;
private int mHardwareYear = 0;
private @Nullable String mHardwareModelName = null;
private long mStartRealtimeMs = 0;
@@ -391,6 +418,7 @@
mGnssHal = Objects.requireNonNull(gnssHal);
mEmergencyHelper = injector.getEmergencyHelper();
mConfiguration = configuration;
+ mHandler = FgThread.getHandler();
}
public void addBaseCallbacks(BaseCallbacks callbacks) {
@@ -532,8 +560,8 @@
/**
* Returns the latest power stats from the GNSS HAL.
*/
- public @Nullable GnssPowerStats getPowerStats() {
- return mPowerStats;
+ public @Nullable GnssPowerStats getLastKnownPowerStats() {
+ return mLastKnownPowerStats;
}
/**
@@ -931,10 +959,49 @@
/**
* Request an eventual update of GNSS power statistics.
+ *
+ * @param executor Executor that will run {@code callback}
+ * @param callback Called with non-null power stats if they were obtained in time, called with
+ * {@code null} if stats could not be obtained in time.
*/
- public void requestPowerStats() {
+ public void requestPowerStats(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull PowerStatsCallback callback) {
Preconditions.checkState(mRegistered);
- mGnssHal.requestPowerStats();
+ synchronized (mPowerStatsLock) {
+ mPendingPowerStatsCallbacks.add(powerStats -> {
+ Binder.withCleanCallingIdentity(
+ () -> executor.execute(() -> callback.onReportPowerStats(powerStats)));
+ });
+ if (mPendingPowerStatsCallbacks.size() == 1) {
+ mGnssHal.requestPowerStats();
+ mHandler.postDelayed(mPowerStatsTimeoutCallback,
+ POWER_STATS_REQUEST_TIMEOUT_MILLIS);
+ }
+ }
+ }
+
+ /**
+ * Request GNSS power statistics and blocks for a short time waiting for the result.
+ *
+ * @return non-null power stats, or {@code null} if stats could not be obtained in time.
+ */
+ public @Nullable GnssPowerStats requestPowerStatsBlocking() {
+ AtomicReference<GnssPowerStats> statsWrapper = new AtomicReference<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ requestPowerStats(Runnable::run, powerStats -> {
+ statsWrapper.set(powerStats);
+ latch.countDown();
+ });
+
+ try {
+ latch.await(POWER_STATS_REQUEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Log.d(TAG, "Interrupted while waiting for power stats");
+ Thread.currentThread().interrupt();
+ }
+
+ return statsWrapper.get();
}
/**
@@ -1167,7 +1234,14 @@
@NativeEntryPoint
void reportGnssPowerStats(GnssPowerStats powerStats) {
- mPowerStats = powerStats;
+ synchronized (mPowerStatsLock) {
+ mHandler.removeCallbacks(mPowerStatsTimeoutCallback);
+ if (powerStats != null) {
+ mLastKnownPowerStats = powerStats;
+ }
+ mPendingPowerStatsCallbacks.forEach(cb -> cb.onReportPowerStats(powerStats));
+ mPendingPowerStatsCallbacks.clear();
+ }
}
@NativeEntryPoint
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index bbb19e3..e3d5c54 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -553,7 +553,8 @@
mProjectionGrant.getLaunchCookie() == null ? null
: mProjectionGrant.getLaunchCookie().binder;
setReviewedConsentSessionLocked(
- ContentRecordingSession.createTaskSession(taskWindowContainerToken));
+ ContentRecordingSession.createTaskSession(
+ taskWindowContainerToken, mProjectionGrant.mTaskId));
break;
}
}
@@ -977,6 +978,7 @@
private IBinder mToken;
private IBinder.DeathRecipient mDeathEater;
private boolean mRestoreSystemAlertWindow;
+ private int mTaskId = -1;
private LaunchCookie mLaunchCookie = null;
// Values for tracking token validity.
@@ -1197,12 +1199,26 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
@Override // Binder call
+ public void setTaskId(int taskId) {
+ setTaskId_enforcePermission();
+ mTaskId = taskId;
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ @Override // Binder call
public LaunchCookie getLaunchCookie() {
getLaunchCookie_enforcePermission();
return mLaunchCookie;
}
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ @Override // Binder call
+ public int getTaskId() {
+ getTaskId_enforcePermission();
+ return mTaskId;
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
@Override
public boolean isValid() {
isValid_enforcePermission();
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 13429db..a7e14d9 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -1540,9 +1540,7 @@
private boolean isAvalancheExemptedFullVolume(final NotificationRecord record) {
// important conversation
- if (record.isConversation()
- && (record.getImportance() > NotificationManager.IMPORTANCE_DEFAULT
- || record.getChannel().isImportantConversation())) {
+ if (record.isConversation() && record.getChannel().isImportantConversation()) {
return true;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 44e7694..c7b0f7d 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5246,28 +5246,8 @@
@Override
public void cancelNotificationFromListener(INotificationListener token, String pkg,
String tag, int id) {
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- final long identity = Binder.clearCallingIdentity();
- try {
- synchronized (mNotificationLock) {
- final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
- int cancelReason = REASON_LISTENER_CANCEL;
- if (mAssistants.isServiceTokenValidLocked(token)) {
- cancelReason = REASON_ASSISTANT_CANCEL;
- }
- if (info.supportsProfiles()) {
- Slog.e(TAG, "Ignoring deprecated cancelNotification(pkg, tag, id) "
- + "from " + info.component
- + " use cancelNotification(key) instead.");
- } else {
- cancelNotificationFromListenerLocked(info, callingUid, callingPid,
- pkg, tag, id, info.userid, cancelReason);
- }
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ Slog.e(TAG, "Ignoring deprecated cancelNotification(pkg, tag, id) use " +
+ "cancelNotification(key) instead.");
}
/**
@@ -7794,25 +7774,45 @@
}
private void checkRemoteViews(String pkg, String tag, int id, Notification notification) {
- if (removeRemoteView(pkg, tag, id, notification.contentView)) {
+ if (android.app.Flags.removeRemoteViews()) {
+ if (notification.contentView != null || notification.bigContentView != null
+ || notification.headsUpContentView != null
+ || (notification.publicVersion != null
+ && (notification.publicVersion.contentView != null
+ || notification.publicVersion.bigContentView != null
+ || notification.publicVersion.headsUpContentView != null))) {
+ Slog.i(TAG, "Removed customViews for " + pkg);
+ mUsageStats.registerImageRemoved(pkg);
+ }
notification.contentView = null;
- }
- if (removeRemoteView(pkg, tag, id, notification.bigContentView)) {
notification.bigContentView = null;
- }
- if (removeRemoteView(pkg, tag, id, notification.headsUpContentView)) {
notification.headsUpContentView = null;
- }
- if (notification.publicVersion != null) {
- if (removeRemoteView(pkg, tag, id, notification.publicVersion.contentView)) {
+ if (notification.publicVersion != null) {
notification.publicVersion.contentView = null;
- }
- if (removeRemoteView(pkg, tag, id, notification.publicVersion.bigContentView)) {
notification.publicVersion.bigContentView = null;
- }
- if (removeRemoteView(pkg, tag, id, notification.publicVersion.headsUpContentView)) {
notification.publicVersion.headsUpContentView = null;
}
+ } else {
+ if (removeRemoteView(pkg, tag, id, notification.contentView)) {
+ notification.contentView = null;
+ }
+ if (removeRemoteView(pkg, tag, id, notification.bigContentView)) {
+ notification.bigContentView = null;
+ }
+ if (removeRemoteView(pkg, tag, id, notification.headsUpContentView)) {
+ notification.headsUpContentView = null;
+ }
+ if (notification.publicVersion != null) {
+ if (removeRemoteView(pkg, tag, id, notification.publicVersion.contentView)) {
+ notification.publicVersion.contentView = null;
+ }
+ if (removeRemoteView(pkg, tag, id, notification.publicVersion.bigContentView)) {
+ notification.publicVersion.bigContentView = null;
+ }
+ if (removeRemoteView(pkg, tag, id, notification.publicVersion.headsUpContentView)) {
+ notification.publicVersion.headsUpContentView = null;
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ebdca5b..29acbcd 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2308,7 +2308,7 @@
}
@Override
- public @NonNull int getProfileAccessibilityLabelResId(@UserIdInt int userId) {
+ public @StringRes int getProfileAccessibilityLabelResId(@UserIdInt int userId) {
checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId,
"getProfileAccessibilityLabelResId");
final UserInfo userInfo = getUserInfoNoChecks(userId);
diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java
index c6f36bf..19410e5 100644
--- a/services/core/java/com/android/server/pm/UserTypeDetails.java
+++ b/services/core/java/com/android/server/pm/UserTypeDetails.java
@@ -164,7 +164,7 @@
* Resource ID ({@link StringRes}) of the accessibility string that describes the user type.
* This is used by accessibility services like Talkback.
*/
- private final @Nullable int mAccessibilityString;
+ private final @StringRes int mAccessibilityString;
/**
* The default {@link UserProperties} for the user type.
@@ -183,7 +183,7 @@
@Nullable Bundle defaultSystemSettings,
@Nullable Bundle defaultSecureSettings,
@Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters,
- @Nullable int accessibilityString,
+ @StringRes int accessibilityString,
@NonNull UserProperties defaultUserProperties) {
this.mName = name;
this.mEnabled = enabled;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index db18276..12e5180 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -341,10 +341,11 @@
static final int SHORT_PRESS_SLEEP_GO_TO_SLEEP = 0;
static final int SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME = 1;
- // must match: config_shortPressOnSettingsBehavior in config.xml
- static final int SHORT_PRESS_SETTINGS_NOTHING = 0;
- static final int SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL = 1;
- static final int LAST_SHORT_PRESS_SETTINGS_BEHAVIOR = SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL;
+ // must match: config_settingsKeyBehavior in config.xml
+ static final int SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY = 0;
+ static final int SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL = 1;
+ static final int SETTINGS_KEY_BEHAVIOR_NOTHING = 2;
+ static final int LAST_SETTINGS_KEY_BEHAVIOR = SETTINGS_KEY_BEHAVIOR_NOTHING;
static final int PENDING_KEY_NULL = -1;
@@ -370,8 +371,8 @@
static final int TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY = 1;
// Must match: config_searchKeyBehavior in config.xml
- static final int SEARCH_BEHAVIOR_DEFAULT_SEARCH = 0;
- static final int SEARCH_BEHAVIOR_TARGET_ACTIVITY = 1;
+ static final int SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH = 0;
+ static final int SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY = 1;
static public final String SYSTEM_DIALOG_REASON_KEY = "reason";
static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
@@ -643,8 +644,8 @@
// What we do when the user double-taps on home
int mDoubleTapOnHomeBehavior;
- // What we do when the user presses on settings
- int mShortPressOnSettingsBehavior;
+ // What we do when the user presses the settings key
+ int mSettingsKeyBehavior;
// Must match config_primaryShortPressTargetActivity in config.xml
ComponentName mPrimaryShortPressTargetActivity;
@@ -2858,11 +2859,11 @@
mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE;
}
- mShortPressOnSettingsBehavior = res.getInteger(
- com.android.internal.R.integer.config_shortPressOnSettingsBehavior);
- if (mShortPressOnSettingsBehavior < SHORT_PRESS_SETTINGS_NOTHING
- || mShortPressOnSettingsBehavior > LAST_SHORT_PRESS_SETTINGS_BEHAVIOR) {
- mShortPressOnSettingsBehavior = SHORT_PRESS_SETTINGS_NOTHING;
+ mSettingsKeyBehavior = res.getInteger(
+ com.android.internal.R.integer.config_settingsKeyBehavior);
+ if (mSettingsKeyBehavior < SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY
+ || mSettingsKeyBehavior > LAST_SETTINGS_KEY_BEHAVIOR) {
+ mSettingsKeyBehavior = SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY;
}
}
@@ -3694,12 +3695,12 @@
case KeyEvent.KEYCODE_SEARCH:
if (firstDown && !keyguardOn) {
switch (mSearchKeyBehavior) {
- case SEARCH_BEHAVIOR_TARGET_ACTIVITY: {
+ case SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY: {
launchTargetSearchActivity();
logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_SEARCH);
return true;
}
- case SEARCH_BEHAVIOR_DEFAULT_SEARCH:
+ case SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH:
default:
break;
}
@@ -3778,14 +3779,16 @@
+ " interceptKeyBeforeQueueing");
return true;
case KeyEvent.KEYCODE_SETTINGS:
- if (mShortPressOnSettingsBehavior == SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL) {
- if (!down) {
+ if (firstDown) {
+ if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL) {
toggleNotificationPanel();
logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL);
+ } else if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY) {
+ showSystemSettings();
+ logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_SYSTEM_SETTINGS);
}
- return true;
}
- break;
+ return true;
case KeyEvent.KEYCODE_STEM_PRIMARY:
if (prepareToSendSystemKeyToApplication(focusedToken, event)) {
// Send to app.
@@ -6563,8 +6566,8 @@
pw.print("mLongPressOnPowerBehavior=");
pw.println(longPressOnPowerBehaviorToString(mLongPressOnPowerBehavior));
pw.print(prefix);
- pw.print("mShortPressOnSettingsBehavior=");
- pw.println(shortPressOnSettingsBehaviorToString(mShortPressOnSettingsBehavior));
+ pw.print("mSettingsKeyBehavior=");
+ pw.println(settingsKeyBehaviorToString(mSettingsKeyBehavior));
pw.print(prefix);
pw.print("mLongPressOnPowerAssistantTimeoutMs=");
pw.println(mLongPressOnPowerAssistantTimeoutMs);
@@ -6766,12 +6769,14 @@
}
}
- private static String shortPressOnSettingsBehaviorToString(int behavior) {
+ private static String settingsKeyBehaviorToString(int behavior) {
switch (behavior) {
- case SHORT_PRESS_SETTINGS_NOTHING:
- return "SHORT_PRESS_SETTINGS_NOTHING";
- case SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL:
- return "SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL";
+ case SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY:
+ return "SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY";
+ case SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL:
+ return "SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL";
+ case SETTINGS_KEY_BEHAVIOR_NOTHING:
+ return "SETTINGS_KEY_BEHAVIOR_NOTHING";
default:
return Integer.toString(behavior);
}
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index afcf49d..6b7f2fa 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -54,6 +54,7 @@
import android.util.Log;
import android.util.Slog;
import android.util.TimingsTraceLog;
+import android.view.SurfaceControl;
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
@@ -459,6 +460,10 @@
metricShutdownStart();
metricStarted(METRIC_SYSTEM_SERVER);
+ // Notify SurfaceFlinger that the device is shutting down.
+ // Transaction traces should be captured at this stage.
+ SurfaceControl.notifyShutdown();
+
// Start dumping check points for this shutdown in a separate thread.
Thread dumpCheckPointsThread = ShutdownCheckPoints.newDumpThread(
new File(CHECK_POINTS_FILE_BASENAME));
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
index e1b4b88..fbdba4f 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
@@ -19,6 +19,7 @@
import android.annotation.CurrentTimeMillisLong;
import android.annotation.DurationMillisLong;
import android.annotation.NonNull;
+import android.os.BatteryStats;
import android.os.UserHandle;
import android.text.format.DateFormat;
import android.util.IndentingPrintWriter;
@@ -155,11 +156,17 @@
int powerComponentId = powerStats.descriptor.powerComponentId;
for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
if (stats.powerComponentId == powerComponentId) {
- stats.addPowerStats(powerStats, time);
+ stats.getConfig().getProcessor().addPowerStats(stats, powerStats, time);
}
}
}
+ public void noteStateChange(BatteryStats.HistoryItem item) {
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.getConfig().getProcessor().noteStateChange(stats, item);
+ }
+ }
+
void reset() {
mClockUpdates.clear();
mDurationMs = 0;
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
index 884c26c..1ff7cb7 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
@@ -205,7 +205,7 @@
private static final PowerStatsProcessor NO_OP_PROCESSOR = new PowerStatsProcessor() {
@Override
- void finish(PowerComponentAggregatedPowerStats stats) {
+ void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) {
}
};
}
diff --git a/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsLayout.java b/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsLayout.java
new file mode 100644
index 0000000..64c3446
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsLayout.java
@@ -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.server.power.stats;
+
+class BinaryStatePowerStatsLayout extends PowerStatsLayout {
+ BinaryStatePowerStatsLayout() {
+ addDeviceSectionUsageDuration();
+ // Add a section for consumed energy, even if the specific device does not
+ // have support EnergyConsumers. This is done to guarantee format compatibility between
+ // PowerStats created by a PowerStatsCollector and those produced by a PowerStatsProcessor.
+ addDeviceSectionEnergyConsumers(1);
+ addDeviceSectionPowerEstimate();
+
+ addUidSectionUsageDuration();
+ addUidSectionPowerEstimate();
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java
new file mode 100644
index 0000000..490bd5e
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java
@@ -0,0 +1,276 @@
+/*
+ * 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.power.stats;
+
+import android.annotation.IntDef;
+import android.os.BatteryStats;
+import android.os.PersistableBundle;
+import android.os.Process;
+
+import com.android.internal.os.PowerStats;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+abstract class BinaryStatePowerStatsProcessor extends PowerStatsProcessor {
+ static final int STATE_OFF = 0;
+ static final int STATE_ON = 1;
+
+ @IntDef(flag = true, prefix = {"STATE_"}, value = {
+ STATE_OFF,
+ STATE_ON,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ protected @interface BinaryState {
+ }
+
+ private final int mPowerComponentId;
+ private final PowerStatsUidResolver mUidResolver;
+ private final UsageBasedPowerEstimator mUsageBasedPowerEstimator;
+ private boolean mEnergyConsumerSupported;
+ private int mInitiatingUid = Process.INVALID_UID;
+ private @BinaryState int mLastState = STATE_OFF;
+ private long mLastStateTimestamp;
+ private long mLastUpdateTimestamp;
+
+ private PowerStats.Descriptor mDescriptor;
+ private final BinaryStatePowerStatsLayout mStatsLayout = new BinaryStatePowerStatsLayout();
+ private PowerStats mPowerStats;
+ private PowerEstimationPlan mPlan;
+ private long[] mTmpDeviceStatsArray;
+ private long[] mTmpUidStatsArray;
+
+ BinaryStatePowerStatsProcessor(int powerComponentId,
+ PowerStatsUidResolver uidResolver, double averagePowerMilliAmp) {
+ mPowerComponentId = powerComponentId;
+ mUsageBasedPowerEstimator = new UsageBasedPowerEstimator(averagePowerMilliAmp);
+ mUidResolver = uidResolver;
+ }
+
+ protected abstract @BinaryState int getBinaryState(BatteryStats.HistoryItem item);
+
+ private void ensureInitialized() {
+ if (mDescriptor != null) {
+ return;
+ }
+
+ PersistableBundle extras = new PersistableBundle();
+ mStatsLayout.toExtras(extras);
+ mDescriptor = new PowerStats.Descriptor(mPowerComponentId,
+ mStatsLayout.getDeviceStatsArrayLength(), null, 0,
+ mStatsLayout.getUidStatsArrayLength(), extras);
+ mPowerStats = new PowerStats(mDescriptor);
+ mPowerStats.stats = new long[mDescriptor.statsArrayLength];
+ mTmpDeviceStatsArray = new long[mDescriptor.statsArrayLength];
+ mTmpUidStatsArray = new long[mDescriptor.uidStatsArrayLength];
+ }
+
+ @Override
+ void start(PowerComponentAggregatedPowerStats stats, long timestampMs) {
+ ensureInitialized();
+
+ // Establish a baseline at the beginning of an accumulation pass
+ mLastState = STATE_OFF;
+ mLastStateTimestamp = timestampMs;
+ mInitiatingUid = Process.INVALID_UID;
+ flushPowerStats(stats, mLastStateTimestamp);
+ }
+
+ @Override
+ void noteStateChange(PowerComponentAggregatedPowerStats stats,
+ BatteryStats.HistoryItem item) {
+ @BinaryState int state = getBinaryState(item);
+ if (state == mLastState) {
+ return;
+ }
+
+ if (state == STATE_ON) {
+ if (item.eventCode == (BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+ | BatteryStats.HistoryItem.EVENT_FLAG_START)) {
+ mInitiatingUid = mUidResolver.mapUid(item.eventTag.uid);
+ }
+ } else {
+ recordUsageDuration(item.time);
+ mInitiatingUid = Process.INVALID_UID;
+ if (!mEnergyConsumerSupported) {
+ flushPowerStats(stats, item.time);
+ }
+ }
+ mLastStateTimestamp = item.time;
+ mLastState = state;
+ }
+
+ private void recordUsageDuration(long time) {
+ if (mLastState == STATE_OFF) {
+ return;
+ }
+
+ long durationMs = time - mLastStateTimestamp;
+ mStatsLayout.setUsageDuration(mPowerStats.stats,
+ mStatsLayout.getUsageDuration(mPowerStats.stats) + durationMs);
+
+ if (mInitiatingUid != Process.INVALID_UID) {
+ long[] uidStats = mPowerStats.uidStats.get(mInitiatingUid);
+ if (uidStats == null) {
+ uidStats = new long[mDescriptor.uidStatsArrayLength];
+ mPowerStats.uidStats.put(mInitiatingUid, uidStats);
+ mStatsLayout.setUidUsageDuration(uidStats, durationMs);
+ } else {
+ mStatsLayout.setUsageDuration(mPowerStats.stats,
+ mStatsLayout.getUsageDuration(mPowerStats.stats) + durationMs);
+ }
+ }
+ mLastStateTimestamp = time;
+ }
+
+ void addPowerStats(PowerComponentAggregatedPowerStats stats, PowerStats powerStats,
+ long timestampMs) {
+ ensureInitialized();
+ recordUsageDuration(timestampMs);
+ long consumedEnergy = mStatsLayout.getConsumedEnergy(powerStats.stats, 0);
+ if (consumedEnergy != BatteryStats.POWER_DATA_UNAVAILABLE) {
+ mEnergyConsumerSupported = true;
+ mStatsLayout.setConsumedEnergy(mPowerStats.stats, 0, consumedEnergy);
+ }
+
+ flushPowerStats(stats, timestampMs);
+ }
+
+ private void flushPowerStats(PowerComponentAggregatedPowerStats stats, long timestamp) {
+ mPowerStats.durationMs = timestamp - mLastUpdateTimestamp;
+ stats.addPowerStats(mPowerStats, timestamp);
+
+ Arrays.fill(mPowerStats.stats, 0);
+ mPowerStats.uidStats.clear();
+ mLastUpdateTimestamp = timestamp;
+ }
+
+ private static class Intermediates {
+ public long duration;
+ public double power;
+ }
+
+ @Override
+ void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) {
+ recordUsageDuration(timestampMs);
+ flushPowerStats(stats, timestampMs);
+
+ if (mPlan == null) {
+ mPlan = new PowerEstimationPlan(stats.getConfig());
+ }
+
+ computeDevicePowerEstimates(stats);
+ combineDevicePowerEstimates(stats);
+
+ List<Integer> uids = new ArrayList<>();
+ stats.collectUids(uids);
+
+ computeUidActivityTotals(stats, uids);
+ computeUidPowerEstimates(stats, uids);
+ }
+
+ private void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats) {
+ for (int i = mPlan.deviceStateEstimations.size() - 1; i >= 0; i--) {
+ DeviceStateEstimation estimation = mPlan.deviceStateEstimations.get(i);
+ if (!stats.getDeviceStats(mTmpDeviceStatsArray, estimation.stateValues)) {
+ continue;
+ }
+
+ long duration = mStatsLayout.getUsageDuration(mTmpDeviceStatsArray);
+ if (duration > 0) {
+ double power;
+ if (mEnergyConsumerSupported) {
+ power = uCtoMah(mStatsLayout.getConsumedEnergy(mTmpDeviceStatsArray, 0));
+ } else {
+ power = mUsageBasedPowerEstimator.calculatePower(duration);
+ }
+ mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, power);
+ stats.setDeviceStats(estimation.stateValues, mTmpDeviceStatsArray);
+ }
+ }
+ }
+
+ private void combineDevicePowerEstimates(PowerComponentAggregatedPowerStats stats) {
+ for (int i = mPlan.combinedDeviceStateEstimations.size() - 1; i >= 0; i--) {
+ CombinedDeviceStateEstimate estimation =
+ mPlan.combinedDeviceStateEstimations.get(i);
+ Intermediates intermediates = new Intermediates();
+ estimation.intermediates = intermediates;
+ for (int j = estimation.deviceStateEstimations.size() - 1; j >= 0; j--) {
+ DeviceStateEstimation deviceStateEstimation =
+ estimation.deviceStateEstimations.get(j);
+ if (!stats.getDeviceStats(mTmpDeviceStatsArray,
+ deviceStateEstimation.stateValues)) {
+ continue;
+ }
+ intermediates.power += mStatsLayout.getDevicePowerEstimate(mTmpDeviceStatsArray);
+ }
+ }
+ }
+
+ private void computeUidActivityTotals(PowerComponentAggregatedPowerStats stats,
+ List<Integer> uids) {
+ for (int i = mPlan.uidStateEstimates.size() - 1; i >= 0; i--) {
+ UidStateEstimate uidStateEstimate = mPlan.uidStateEstimates.get(i);
+ Intermediates intermediates =
+ (Intermediates) uidStateEstimate.combinedDeviceStateEstimate.intermediates;
+ for (int j = uids.size() - 1; j >= 0; j--) {
+ int uid = uids.get(j);
+ for (UidStateProportionalEstimate proportionalEstimate :
+ uidStateEstimate.proportionalEstimates) {
+ if (stats.getUidStats(mTmpUidStatsArray, uid,
+ proportionalEstimate.stateValues)) {
+ intermediates.duration +=
+ mStatsLayout.getUidUsageDuration(mTmpUidStatsArray);
+ }
+ }
+ }
+ }
+ }
+
+ private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats,
+ List<Integer> uids) {
+ for (int i = mPlan.uidStateEstimates.size() - 1; i >= 0; i--) {
+ UidStateEstimate uidStateEstimate = mPlan.uidStateEstimates.get(i);
+ Intermediates intermediates =
+ (Intermediates) uidStateEstimate.combinedDeviceStateEstimate.intermediates;
+ if (intermediates.duration == 0) {
+ continue;
+ }
+ List<UidStateProportionalEstimate> proportionalEstimates =
+ uidStateEstimate.proportionalEstimates;
+ for (int j = proportionalEstimates.size() - 1; j >= 0; j--) {
+ UidStateProportionalEstimate proportionalEstimate = proportionalEstimates.get(j);
+ for (int k = uids.size() - 1; k >= 0; k--) {
+ int uid = uids.get(k);
+ if (stats.getUidStats(mTmpUidStatsArray, uid,
+ proportionalEstimate.stateValues)) {
+ double power = intermediates.power
+ * mStatsLayout.getUidUsageDuration(mTmpUidStatsArray)
+ / intermediates.duration;
+ mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
+ stats.setUidStats(uid, proportionalEstimate.stateValues,
+ mTmpUidStatsArray);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/BluetoothPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/BluetoothPowerStatsProcessor.java
index 4d6db97..077b057 100644
--- a/services/core/java/com/android/server/power/stats/BluetoothPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/BluetoothPowerStatsProcessor.java
@@ -86,7 +86,7 @@
}
@Override
- void finish(PowerComponentAggregatedPowerStats stats) {
+ void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) {
if (stats.getPowerStatsDescriptor() == null) {
return;
}
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java
index 57b7259..6da0a8f 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java
@@ -138,7 +138,7 @@
}
@Override
- public void finish(PowerComponentAggregatedPowerStats stats) {
+ public void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) {
if (stats.getPowerStatsDescriptor() == null) {
return;
}
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java
index eebed2f..dcce562 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java
@@ -166,7 +166,7 @@
}
@Override
- void finish(PowerComponentAggregatedPowerStats stats) {
+ void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) {
if (stats.getPowerStatsDescriptor() == null) {
return;
}
diff --git a/services/core/java/com/android/server/power/stats/MultiStateStats.java b/services/core/java/com/android/server/power/stats/MultiStateStats.java
index a822281..c3a0aeb 100644
--- a/services/core/java/com/android/server/power/stats/MultiStateStats.java
+++ b/services/core/java/com/android/server/power/stats/MultiStateStats.java
@@ -41,6 +41,7 @@
private static final String TAG = "MultiStateStats";
private static final String XML_TAG_STATS = "stats";
+ public static final int STATE_DOES_NOT_EXIST = -1;
/**
* A set of states, e.g. on-battery, screen-on, procstate. The state values are integers
@@ -70,6 +71,18 @@
}
/**
+ * Finds state by name in the provided array. If not found, returns STATE_DOES_NOT_EXIST.
+ */
+ public static int findTrackedStateByName(MultiStateStats.States[] states, String name) {
+ for (int i = 0; i < states.length; i++) {
+ if (states[i].getName().equals(name)) {
+ return i;
+ }
+ }
+ return STATE_DOES_NOT_EXIST;
+ }
+
+ /**
* Iterates over all combinations of tracked states and invokes <code>consumer</code>
* for each of them.
*/
diff --git a/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java
index 5c545fd..ec23fa0 100644
--- a/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java
@@ -39,7 +39,7 @@
}
@Override
- void finish(PowerComponentAggregatedPowerStats stats) {
+ void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) {
stats.setPowerStatsDescriptor(mDescriptor);
PowerComponentAggregatedPowerStats mobileRadioStats =
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
index 0528733..85a2293 100644
--- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -16,6 +16,8 @@
package com.android.server.power.stats;
+import static com.android.server.power.stats.MultiStateStats.STATE_DOES_NOT_EXIST;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.UserHandle;
@@ -68,10 +70,12 @@
private MultiStateStats mDeviceStats;
private final SparseArray<MultiStateStats> mStateStats = new SparseArray<>();
private final SparseArray<UidStats> mUidStats = new SparseArray<>();
+ private long[] mZeroArray;
private static class UidStats {
public int[] states;
public MultiStateStats stats;
+ public boolean updated;
}
PowerComponentAggregatedPowerStats(@NonNull AggregatedPowerStats aggregatedPowerStats,
@@ -122,16 +126,18 @@
}
}
- if (mUidStateConfig[stateId].isTracked()) {
+ int uidStateId = MultiStateStats.States
+ .findTrackedStateByName(mUidStateConfig, mDeviceStateConfig[stateId].getName());
+ if (uidStateId != STATE_DOES_NOT_EXIST && mUidStateConfig[uidStateId].isTracked()) {
for (int i = mUidStats.size() - 1; i >= 0; i--) {
PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i);
if (uidStats.stats == null) {
createUidStats(uidStats, timestampMs);
}
- uidStats.states[stateId] = state;
+ uidStats.states[uidStateId] = state;
if (uidStats.stats != null) {
- uidStats.stats.setState(stateId, state, timestampMs);
+ uidStats.stats.setState(uidStateId, state, timestampMs);
}
}
}
@@ -196,6 +202,22 @@
createUidStats(uidStats, timestampMs);
}
uidStats.stats.increment(powerStats.uidStats.valueAt(i), timestampMs);
+ uidStats.updated = true;
+ }
+
+ // For UIDs not mentioned in the PowerStats object, we must assume a 0 increment.
+ // It is essential to call `stats.increment(zero)` in order to record the new
+ // timestamp, which will ensure correct proportional attribution across all UIDs
+ for (int i = mUidStats.size() - 1; i >= 0; i--) {
+ PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i);
+ if (!uidStats.updated && uidStats.stats != null) {
+ if (mZeroArray == null
+ || mZeroArray.length != mPowerStatsDescriptor.uidStatsArrayLength) {
+ mZeroArray = new long[mPowerStatsDescriptor.uidStatsArrayLength];
+ }
+ uidStats.stats.increment(mZeroArray, timestampMs);
+ }
+ uidStats.updated = false;
}
mPowerStatsTimestamp = timestampMs;
@@ -217,10 +239,13 @@
uidStats = new UidStats();
uidStats.states = new int[mUidStateConfig.length];
for (int stateId = 0; stateId < mUidStateConfig.length; stateId++) {
- if (mUidStateConfig[stateId].isTracked()
- && stateId < mDeviceStateConfig.length
- && mDeviceStateConfig[stateId].isTracked()) {
- uidStats.states[stateId] = mDeviceStates[stateId];
+ if (mUidStateConfig[stateId].isTracked()) {
+ int deviceStateId = MultiStateStats.States.findTrackedStateByName(
+ mDeviceStateConfig, mUidStateConfig[stateId].getName());
+ if (deviceStateId != STATE_DOES_NOT_EXIST
+ && mDeviceStateConfig[deviceStateId].isTracked()) {
+ uidStats.states[stateId] = mDeviceStates[deviceStateId];
+ }
}
}
mUidStats.put(uid, uidStats);
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
index 6a4c1f0..6e7fdf1 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
@@ -71,9 +71,13 @@
mStats = new AggregatedPowerStats(mAggregatedPowerStatsConfig);
}
+ start(mStats, startTimeMs);
+
boolean clockUpdateAdded = false;
long baseTime = startTimeMs > 0 ? startTimeMs : UNINITIALIZED;
long lastTime = 0;
+ int lastStates = 0xFFFFFFFF;
+ int lastStates2 = 0xFFFFFFFF;
try (BatteryStatsHistoryIterator iterator = mHistory.iterate(startTimeMs, endTimeMs)) {
while (iterator.hasNext()) {
BatteryStats.HistoryItem item = iterator.next();
@@ -111,6 +115,19 @@
mCurrentScreenState = screenState;
}
+ if ((item.states
+ & BatteryStats.HistoryItem.IMPORTANT_FOR_POWER_STATS_STATES)
+ != lastStates
+ || (item.states2
+ & BatteryStats.HistoryItem.IMPORTANT_FOR_POWER_STATS_STATES2)
+ != lastStates2) {
+ mStats.noteStateChange(item);
+ lastStates = item.states
+ & BatteryStats.HistoryItem.IMPORTANT_FOR_POWER_STATS_STATES;
+ lastStates2 = item.states2
+ & BatteryStats.HistoryItem.IMPORTANT_FOR_POWER_STATS_STATES2;
+ }
+
if (item.processStateChange != null) {
mStats.setUidState(item.processStateChange.uid,
AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
@@ -121,7 +138,7 @@
if (!mStats.isCompatible(item.powerStats)) {
if (lastTime > baseTime) {
mStats.setDuration(lastTime - baseTime);
- finish(mStats);
+ finish(mStats, lastTime);
consumer.accept(mStats);
}
mStats.reset();
@@ -134,7 +151,7 @@
}
if (lastTime > baseTime) {
mStats.setDuration(lastTime - baseTime);
- finish(mStats);
+ finish(mStats, lastTime);
consumer.accept(mStats);
}
@@ -142,12 +159,22 @@
}
}
- private void finish(AggregatedPowerStats stats) {
+ private void start(AggregatedPowerStats stats, long timestampMs) {
for (int i = 0; i < mProcessors.size(); i++) {
PowerComponentAggregatedPowerStats component =
stats.getPowerComponentStats(mProcessors.keyAt(i));
if (component != null) {
- mProcessors.valueAt(i).finish(component);
+ mProcessors.valueAt(i).start(component, timestampMs);
+ }
+ }
+ }
+
+ private void finish(AggregatedPowerStats stats, long timestampMs) {
+ for (int i = 0; i < mProcessors.size(); i++) {
+ PowerComponentAggregatedPowerStats component =
+ stats.getPowerComponentStats(mProcessors.keyAt(i));
+ if (component != null) {
+ mProcessors.valueAt(i).finish(component, timestampMs);
}
}
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsLayout.java b/services/core/java/com/android/server/power/stats/PowerStatsLayout.java
index 58efd94..9624fd2 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsLayout.java
@@ -31,6 +31,7 @@
private static final String EXTRA_DEVICE_DURATION_POSITION = "dd";
private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION = "de";
private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec";
+ private static final String EXTRA_UID_DURATION_POSITION = "ud";
private static final String EXTRA_UID_POWER_POSITION = "up";
protected static final int UNSUPPORTED = -1;
@@ -51,6 +52,7 @@
private int mDeviceEnergyConsumerPosition;
private int mDeviceEnergyConsumerCount;
private int mDevicePowerEstimatePosition = UNSUPPORTED;
+ private int mUidDurationPosition = UNSUPPORTED;
private int mUidPowerEstimatePosition = UNSUPPORTED;
public PowerStatsLayout() {
@@ -158,7 +160,8 @@
* PowerStatsService.
*/
public void addDeviceSectionEnergyConsumers(int energyConsumerCount) {
- mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount, "energy");
+ mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount, "energy",
+ FLAG_OPTIONAL);
mDeviceEnergyConsumerCount = energyConsumerCount;
}
@@ -206,6 +209,13 @@
}
/**
+ * Declare that the UID stats array has a section capturing usage duration
+ */
+ public void addUidSectionUsageDuration() {
+ mUidDurationPosition = addUidSection(1, "time");
+ }
+
+ /**
* Declare that the UID stats array has a section capturing a power estimate
*/
public void addUidSectionPowerEstimate() {
@@ -220,6 +230,20 @@
}
/**
+ * Saves usage duration it in the corresponding element of <code>stats</code>.
+ */
+ public void setUidUsageDuration(long[] stats, long durationMs) {
+ stats[mUidDurationPosition] = durationMs;
+ }
+
+ /**
+ * Extracts the usage duration from a UID stats array.
+ */
+ public long getUidUsageDuration(long[] stats) {
+ return stats[mUidDurationPosition];
+ }
+
+ /**
* Converts the supplied mAh power estimate to a long and saves it in the corresponding
* element of <code>stats</code>.
*/
@@ -244,6 +268,7 @@
extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT,
mDeviceEnergyConsumerCount);
extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition);
+ extras.putInt(EXTRA_UID_DURATION_POSITION, mUidDurationPosition);
extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition);
extras.putString(PowerStats.Descriptor.EXTRA_DEVICE_STATS_FORMAT, mDeviceFormat.toString());
extras.putString(PowerStats.Descriptor.EXTRA_STATE_STATS_FORMAT, mStateFormat.toString());
@@ -258,6 +283,7 @@
mDeviceEnergyConsumerPosition = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION);
mDeviceEnergyConsumerCount = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT);
mDevicePowerEstimatePosition = extras.getInt(EXTRA_DEVICE_POWER_POSITION);
+ mUidDurationPosition = extras.getInt(EXTRA_UID_DURATION_POSITION);
mUidPowerEstimatePosition = extras.getInt(EXTRA_UID_POWER_POSITION);
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
index 2fd0b9a..f257e1a 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
@@ -15,10 +15,16 @@
*/
package com.android.server.power.stats;
+import static com.android.server.power.stats.MultiStateStats.STATE_DOES_NOT_EXIST;
+import static com.android.server.power.stats.MultiStateStats.States.findTrackedStateByName;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.BatteryStats;
import android.util.Log;
+import com.android.internal.os.PowerStats;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -40,10 +46,21 @@
abstract class PowerStatsProcessor {
private static final String TAG = "PowerStatsProcessor";
- private static final int INDEX_DOES_NOT_EXIST = -1;
private static final double MILLIAMPHOUR_PER_MICROCOULOMB = 1.0 / 1000.0 / 60.0 / 60.0;
- abstract void finish(PowerComponentAggregatedPowerStats stats);
+ void start(PowerComponentAggregatedPowerStats stats, long timestampMs) {
+ }
+
+ void noteStateChange(PowerComponentAggregatedPowerStats stats,
+ BatteryStats.HistoryItem item) {
+ }
+
+ void addPowerStats(PowerComponentAggregatedPowerStats stats, PowerStats powerStats,
+ long timestampMs) {
+ stats.addPowerStats(powerStats, timestampMs);
+ }
+
+ abstract void finish(PowerComponentAggregatedPowerStats stats, long timestampMs);
protected static class PowerEstimationPlan {
private final AggregatedPowerStatsConfig.PowerComponent mConfig;
@@ -79,7 +96,7 @@
}
int index = findTrackedStateByName(uidStateConfig, deviceStateConfig[i].getName());
- if (index != INDEX_DOES_NOT_EXIST && uidStateConfig[index].isTracked()) {
+ if (index != STATE_DOES_NOT_EXIST && uidStateConfig[index].isTracked()) {
deviceStatesTrackedPerUid[i] = deviceStateConfig[i];
}
}
@@ -131,7 +148,7 @@
}
int index = findTrackedStateByName(deviceStateConfig, uidStateConfig[i].getName());
- if (index != INDEX_DOES_NOT_EXIST && deviceStateConfig[index].isTracked()) {
+ if (index != STATE_DOES_NOT_EXIST && deviceStateConfig[index].isTracked()) {
uidStatesTrackedForDevice[i] = uidStateConfig[i];
} else {
uidStatesNotTrackedForDevice[i] = uidStateConfig[i];
@@ -303,15 +320,6 @@
}
}
- private static int findTrackedStateByName(MultiStateStats.States[] states, String name) {
- for (int i = 0; i < states.length; i++) {
- if (states[i].getName().equals(name)) {
- return i;
- }
- }
- return INDEX_DOES_NOT_EXIST;
- }
-
@NonNull
private static String concatLabels(MultiStateStats.States[] config,
@AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java
index a4a2e18..4e035c3 100644
--- a/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java
@@ -114,7 +114,7 @@
}
@Override
- void finish(PowerComponentAggregatedPowerStats stats) {
+ void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) {
if (stats.getPowerStatsDescriptor() == null) {
return;
}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index f62c76e..e00e813 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -29,6 +29,7 @@
import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustListener;
import android.app.trust.ITrustManager;
+import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -47,6 +48,8 @@
import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.location.ISignificantPlaceProvider;
+import android.hardware.location.ISignificantPlaceProviderManager;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -83,6 +86,8 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.SystemService;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.ServiceWatcher;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -248,6 +253,9 @@
private boolean mTrustAgentsCanRun = false;
private int mCurrentUser = UserHandle.USER_SYSTEM;
+ private ServiceWatcher mSignificantPlaceServiceWatcher;
+ private volatile boolean mIsInSignificantPlace = false;
+
/**
* A class for providing dependencies to {@link TrustManagerService} in both production and test
* cases.
@@ -310,6 +318,38 @@
mTrustAgentsCanRun = true;
refreshAgentList(UserHandle.USER_ALL);
refreshDeviceLockedForUser(UserHandle.USER_ALL);
+
+ if (android.security.Flags.significantPlaces()) {
+ mSignificantPlaceServiceWatcher = ServiceWatcher.create(mContext, TAG,
+ CurrentUserServiceSupplier.create(
+ mContext,
+ TrustManager.ACTION_BIND_SIGNIFICANT_PLACE_PROVIDER,
+ null,
+ null,
+ null),
+ new ServiceWatcher.ServiceListener<>() {
+ @Override
+ public void onBind(IBinder binder,
+ CurrentUserServiceSupplier.BoundServiceInfo service)
+ throws RemoteException {
+ ISignificantPlaceProvider.Stub.asInterface(binder)
+ .setSignificantPlaceProviderManager(
+ new ISignificantPlaceProviderManager.Stub() {
+ @Override
+ public void setInSignificantPlace(
+ boolean inSignificantPlace) {
+ mIsInSignificantPlace = inSignificantPlace;
+ }
+ });
+ }
+
+ @Override
+ public void onUnbind() {
+ mIsInSignificantPlace = false;
+ }
+ });
+ mSignificantPlaceServiceWatcher.register();
+ }
} else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
maybeEnableFactoryTrustAgents(UserHandle.USER_SYSTEM);
}
@@ -1651,6 +1691,11 @@
}
}
+ @Override
+ public boolean isInSignificantPlace() {
+ return mIsInSignificantPlace;
+ }
+
private void enforceReportPermission() {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE, "reporting trust events");
@@ -1680,6 +1725,9 @@
for (UserInfo user : userInfos) {
dumpUser(fout, user, user.id == mCurrentUser);
}
+ if (mSignificantPlaceServiceWatcher != null) {
+ mSignificantPlaceServiceWatcher.dump(fout);
+ }
}
}, 1500);
}
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index 110100a..8d14c1d 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -35,6 +35,7 @@
import android.content.Intent;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
+import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
@@ -189,6 +190,9 @@
@Override
protected int getMaximumTemporaryServiceDurationMs() {
+ if (Build.isDebuggable()) {
+ return Integer.MAX_VALUE;
+ }
return MAX_TEMPORARY_SERVICE_DURATION_MS;
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index af59567..eb6262c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -315,6 +315,7 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
@@ -356,6 +357,7 @@
import android.view.Surface.Rotation;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
+import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
@@ -2554,7 +2556,7 @@
}
final WindowState mainWin = findMainWindow(false /* includeStartingApp */);
- if (mainWin != null && mainWin.mWinAnimator.getShown()) {
+ if (mainWin != null && mainWin.isDrawn()) {
// App already has a visible window...why would you want a starting window?
return false;
}
@@ -8560,6 +8562,13 @@
if (isFixedOrientationLetterboxAllowed) {
resolveFixedOrientationConfiguration(newParentConfiguration);
}
+ // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
+ // are already calculated in resolveFixedOrientationConfiguration.
+ // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
+ if (!isLetterboxedForFixedOrientationAndAspectRatio()
+ && !mLetterboxUiController.hasFullscreenOverride()) {
+ resolveAspectRatioRestriction(newParentConfiguration);
+ }
final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets();
if (compatDisplayInsets != null) {
resolveSizeCompatModeConfiguration(newParentConfiguration, compatDisplayInsets);
@@ -8572,14 +8581,6 @@
if (!matchParentBounds()) {
computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
}
- // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
- // are already calculated in resolveFixedOrientationConfiguration, or if in size compat
- // mode, it should already be calculated in resolveSizeCompatModeConfiguration.
- // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
- }
- if (!isLetterboxedForFixedOrientationAndAspectRatio() && !mInSizeCompatModeForBounds
- && !mLetterboxUiController.hasFullscreenOverride()) {
- resolveAspectRatioRestriction(newParentConfiguration);
}
if (isFixedOrientationLetterboxAllowed || compatDisplayInsets != null
@@ -8801,7 +8802,7 @@
return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
}
// Letterbox for limited aspect ratio.
- if (mIsAspectRatioApplied) {
+ if (isLetterboxedForAspectRatioOnly()) {
return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
}
@@ -8830,13 +8831,27 @@
final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
final float screenResolvedBoundsWidth = screenResolvedBounds.width();
final float parentAppBoundsWidth = parentAppBounds.width();
+ final boolean isImmersiveMode = isImmersiveMode(parentBounds);
+ final Insets navBarInsets;
+ if (isImmersiveMode) {
+ navBarInsets = mDisplayContent.getInsetsStateController()
+ .getRawInsetsState().calculateInsets(
+ parentBounds,
+ WindowInsets.Type.navigationBars(),
+ true /* ignoreVisibility */);
+ } else {
+ navBarInsets = Insets.NONE;
+ }
// Horizontal position
int offsetX = 0;
if (parentBounds.width() != screenResolvedBoundsWidth) {
if (screenResolvedBoundsWidth <= parentAppBoundsWidth) {
float positionMultiplier = mLetterboxUiController.getHorizontalPositionMultiplier(
newParentConfiguration);
- offsetX = Math.max(0, (int) Math.ceil((parentAppBoundsWidth
+ // If in immersive mode, always align to right and overlap right insets (task bar)
+ // as they are transient and hidden. This removes awkward right spacing.
+ final int appWidth = (int) (parentAppBoundsWidth + navBarInsets.right);
+ offsetX = Math.max(0, (int) Math.ceil((appWidth
- screenResolvedBoundsWidth) * positionMultiplier)
// This is added to make sure that insets added inside
// CompatDisplayInsets#getContainerBounds() do not break the alignment
@@ -8856,9 +8871,8 @@
newParentConfiguration);
// If in immersive mode, always align to bottom and overlap bottom insets (nav bar,
// task bar) as they are transient and hidden. This removes awkward bottom spacing.
- final float newHeight = mDisplayContent.getDisplayPolicy().isImmersiveMode()
- ? parentBoundsHeight : parentAppBoundsHeight;
- offsetY = Math.max(0, (int) Math.ceil((newHeight
+ final int appHeight = (int) (parentAppBoundsHeight + navBarInsets.bottom);
+ offsetY = Math.max(0, (int) Math.ceil((appHeight
- screenResolvedBoundsHeight) * positionMultiplier)
// This is added to make sure that insets added inside
// CompatDisplayInsets#getContainerBounds() do not break the alignment
@@ -8878,7 +8892,8 @@
// If the top is aligned with parentAppBounds add the vertical insets back so that the app
// content aligns with the status bar
- if (resolvedConfig.windowConfiguration.getAppBounds().top == parentAppBounds.top) {
+ if (resolvedConfig.windowConfiguration.getAppBounds().top == parentAppBounds.top
+ && !isImmersiveMode) {
resolvedConfig.windowConfiguration.getBounds().top = parentBounds.top;
if (mSizeCompatBounds != null) {
mSizeCompatBounds.top = parentBounds.top;
@@ -8901,6 +8916,18 @@
}
}
+ boolean isImmersiveMode(@NonNull Rect parentBounds) {
+ if (!mResolveConfigHint.mUseOverrideInsetsForConfig) {
+ return false;
+ }
+ final Insets navBarInsets = mDisplayContent.getInsetsStateController()
+ .getRawInsetsState().calculateInsets(
+ parentBounds,
+ WindowInsets.Type.navigationBars(),
+ false /* ignoreVisibility */);
+ return Insets.NONE.equals(navBarInsets);
+ }
+
@NonNull Rect getScreenResolvedBounds() {
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
@@ -8943,6 +8970,10 @@
return mLetterboxBoundsForFixedOrientationAndAspectRatio != null;
}
+ boolean isLetterboxedForAspectRatioOnly() {
+ return mLetterboxBoundsForAspectRatio != null;
+ }
+
boolean isAspectRatioApplied() {
return mIsAspectRatioApplied;
}
@@ -9235,11 +9266,11 @@
// orientation bounds (stored in resolved bounds) instead of parent bounds since the
// activity will be displayed within them even if it is in size compat mode. They should be
// saved here before resolved bounds are overridden below.
- final Rect containerBounds = isLetterboxedForFixedOrientationAndAspectRatio()
+ final Rect containerBounds = isAspectRatioApplied()
? new Rect(resolvedBounds)
: newParentConfiguration.windowConfiguration.getBounds();
- final Rect containerAppBounds = isLetterboxedForFixedOrientationAndAspectRatio()
- ? new Rect(getResolvedOverrideConfiguration().windowConfiguration.getAppBounds())
+ final Rect containerAppBounds = isAspectRatioApplied()
+ ? new Rect(resolvedConfig.windowConfiguration.getAppBounds())
: newParentConfiguration.windowConfiguration.getAppBounds();
final int requestedOrientation = getRequestedConfigurationOrientation();
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 2cccd33..1a9d211 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -56,6 +56,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Pair;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
@@ -75,6 +76,7 @@
* is no guarantee that other system services are already present.
*/
class ActivityStartInterceptor {
+ private static final String TAG = "ActivityStartInterceptor";
private final ActivityTaskManagerService mService;
private final ActivityTaskSupervisor mSupervisor;
@@ -284,6 +286,8 @@
if (!mUserManager.isQuietModeEnabled(UserHandle.of(mUserId))) {
return false;
}
+ Slog.i(TAG, "Intent : " + mIntent + " intercepted for user: " + mUserId
+ + " because quiet mode is enabled.");
IntentSender target = createIntentSenderForOriginalIntent(mCallingUid,
FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index f06d3af..a10f7e7 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1519,7 +1519,10 @@
}
try {
- if ((flags & ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) {
+ // We allow enter PiP for previous front task if not requested otherwise via options.
+ boolean shouldCauseEnterPip = options == null
+ || !options.disallowEnterPictureInPictureWhileLaunching();
+ if ((flags & ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0 && shouldCauseEnterPip) {
mUserLeaving = true;
}
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index a9450c4..eb85c1a 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -505,10 +505,10 @@
*/
boolean shouldFreezeInsetsPosition(WindowState w) {
// Non-change transition (OP_APP_SWITCH) and METHOD_BLAST don't use screenshot so the
- // insets should keep original position before the start transaction is applied.
+ // insets should keep original position before the window is done with new rotation.
return mTransitionOp != OP_LEGACY && (isSeamlessTransition()
|| TransitionController.SYNC_METHOD == BLASTSyncEngine.METHOD_BLAST)
- && !mIsStartTransactionCommitted && canBeAsync(w.mToken) && isTargetToken(w.mToken);
+ && canBeAsync(w.mToken) && isTargetToken(w.mToken);
}
/** Returns true if there won't be a screen rotation animation (screenshot-based). */
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index 1128328..50ac801 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -18,8 +18,8 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.LaunchParamsModifierUtils.applyLayoutGravity;
-import static com.android.server.wm.LaunchParamsModifierUtils.calculateLayoutBounds;
+import static com.android.server.wm.LaunchParamsUtil.applyLayoutGravity;
+import static com.android.server.wm.LaunchParamsUtil.calculateLayoutBounds;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 84dadab..d0086aa 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -145,6 +145,7 @@
import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wallpaper.WallpaperManagerInternal;
+import com.android.wm.shell.Flags;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -1805,7 +1806,8 @@
updateConfigurationAndScreenSizeDependentBehaviors();
final boolean shouldAttach =
- res.getBoolean(R.bool.config_attachNavBarToAppDuringTransition);
+ res.getBoolean(R.bool.config_attachNavBarToAppDuringTransition)
+ && !Flags.enableTinyTaskbar();
if (mShouldAttachNavBarToAppDuringTransition != shouldAttach) {
mShouldAttachNavBarToAppDuringTransition = shouldAttach;
}
diff --git a/services/core/java/com/android/server/wm/LaunchParamsModifierUtils.java b/services/core/java/com/android/server/wm/LaunchParamsModifierUtils.java
deleted file mode 100644
index db54647..0000000
--- a/services/core/java/com/android/server/wm/LaunchParamsModifierUtils.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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.wm;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.pm.ActivityInfo;
-import android.graphics.Rect;
-import android.util.Size;
-import android.view.Gravity;
-
-class LaunchParamsModifierUtils {
-
- /**
- * Calculates bounds based on window layout size manifest values. These can include width,
- * height, width fraction and height fraction. In the event only one dimension of values are
- * specified in the manifest (e.g. width but no height value), the corresponding display area
- * dimension will be used as the default value unless some desired sizes have been specified.
- */
- static void calculateLayoutBounds(@NonNull Rect stableBounds,
- @NonNull ActivityInfo.WindowLayout windowLayout, @NonNull Rect inOutBounds,
- @Nullable Size desiredSize) {
- final int defaultWidth = stableBounds.width();
- final int defaultHeight = stableBounds.height();
- int width;
- int height;
-
- if (desiredSize == null) {
- // If desired bounds have not been specified, use the exiting default bounds as the
- // desired.
- desiredSize = new Size(stableBounds.width(), stableBounds.height());
- }
-
- width = desiredSize.getWidth();
- if (windowLayout.width > 0 && windowLayout.width < defaultWidth) {
- width = windowLayout.width;
- } else if (windowLayout.widthFraction > 0 && windowLayout.widthFraction < 1.0f) {
- width = (int) (defaultWidth * windowLayout.widthFraction);
- }
-
- height = desiredSize.getHeight();
- if (windowLayout.height > 0 && windowLayout.height < defaultHeight) {
- height = windowLayout.height;
- } else if (windowLayout.heightFraction > 0 && windowLayout.heightFraction < 1.0f) {
- height = (int) (defaultHeight * windowLayout.heightFraction);
- }
-
- inOutBounds.set(0, 0, width, height);
- }
-
- /**
- * Applies a vertical and horizontal gravity on the inOutBounds in relation to the stableBounds.
- */
- static void applyLayoutGravity(int verticalGravity, int horizontalGravity,
- @NonNull Rect inOutBounds, @NonNull Rect stableBounds) {
- final int width = inOutBounds.width();
- final int height = inOutBounds.height();
-
- final float fractionOfHorizontalOffset;
- switch (horizontalGravity) {
- case Gravity.LEFT:
- fractionOfHorizontalOffset = 0f;
- break;
- case Gravity.RIGHT:
- fractionOfHorizontalOffset = 1f;
- break;
- default:
- fractionOfHorizontalOffset = 0.5f;
- }
-
- final float fractionOfVerticalOffset;
- switch (verticalGravity) {
- case Gravity.TOP:
- fractionOfVerticalOffset = 0f;
- break;
- case Gravity.BOTTOM:
- fractionOfVerticalOffset = 1f;
- break;
- default:
- fractionOfVerticalOffset = 0.5f;
- }
-
- inOutBounds.offsetTo(stableBounds.left, stableBounds.top);
- final int xOffset = (int) (fractionOfHorizontalOffset * (stableBounds.width() - width));
- final int yOffset = (int) (fractionOfVerticalOffset * (stableBounds.height() - height));
- inOutBounds.offset(xOffset, yOffset);
- }
-}
diff --git a/services/core/java/com/android/server/wm/LaunchParamsUtil.java b/services/core/java/com/android/server/wm/LaunchParamsUtil.java
index cd071af..9416daf 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsUtil.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsUtil.java
@@ -23,9 +23,11 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.util.Size;
+import android.view.Gravity;
import android.view.View;
/**
@@ -195,4 +197,79 @@
}
inOutBounds.offset(dx, dy);
}
+
+ /**
+ * Calculates bounds based on window layout size manifest values. These can include width,
+ * height, width fraction and height fraction. In the event only one dimension of values are
+ * specified in the manifest (e.g. width but no height value), the corresponding display area
+ * dimension will be used as the default value unless some desired sizes have been specified.
+ */
+ static void calculateLayoutBounds(@NonNull Rect stableBounds,
+ @NonNull ActivityInfo.WindowLayout windowLayout, @NonNull Rect inOutBounds,
+ @Nullable Size desiredSize) {
+ final int defaultWidth = stableBounds.width();
+ final int defaultHeight = stableBounds.height();
+ int width;
+ int height;
+
+ if (desiredSize == null) {
+ // If desired bounds have not been specified, use the exiting default bounds as the
+ // desired.
+ desiredSize = new Size(stableBounds.width(), stableBounds.height());
+ }
+
+ width = desiredSize.getWidth();
+ if (windowLayout.width > 0 && windowLayout.width < defaultWidth) {
+ width = windowLayout.width;
+ } else if (windowLayout.widthFraction > 0 && windowLayout.widthFraction < 1.0f) {
+ width = (int) (defaultWidth * windowLayout.widthFraction);
+ }
+
+ height = desiredSize.getHeight();
+ if (windowLayout.height > 0 && windowLayout.height < defaultHeight) {
+ height = windowLayout.height;
+ } else if (windowLayout.heightFraction > 0 && windowLayout.heightFraction < 1.0f) {
+ height = (int) (defaultHeight * windowLayout.heightFraction);
+ }
+
+ inOutBounds.set(0, 0, width, height);
+ }
+
+ /**
+ * Applies a vertical and horizontal gravity on the inOutBounds in relation to the stableBounds.
+ */
+ static void applyLayoutGravity(int verticalGravity, int horizontalGravity,
+ @NonNull Rect inOutBounds, @NonNull Rect stableBounds) {
+ final int width = inOutBounds.width();
+ final int height = inOutBounds.height();
+
+ final float fractionOfHorizontalOffset;
+ switch (horizontalGravity) {
+ case Gravity.LEFT:
+ fractionOfHorizontalOffset = 0f;
+ break;
+ case Gravity.RIGHT:
+ fractionOfHorizontalOffset = 1f;
+ break;
+ default:
+ fractionOfHorizontalOffset = 0.5f;
+ }
+
+ final float fractionOfVerticalOffset;
+ switch (verticalGravity) {
+ case Gravity.TOP:
+ fractionOfVerticalOffset = 0f;
+ break;
+ case Gravity.BOTTOM:
+ fractionOfVerticalOffset = 1f;
+ break;
+ default:
+ fractionOfVerticalOffset = 0.5f;
+ }
+
+ inOutBounds.offsetTo(stableBounds.left, stableBounds.top);
+ final int xOffset = (int) (fractionOfHorizontalOffset * (stableBounds.width() - width));
+ final int yOffset = (int) (fractionOfVerticalOffset * (stableBounds.height() - height));
+ inOutBounds.offset(xOffset, yOffset);
+ }
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 6e11e08..b8d0694 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1704,7 +1704,7 @@
if (mainWin.isLetterboxedForDisplayCutout()) {
return "DISPLAY_CUTOUT";
}
- if (mActivityRecord.isAspectRatioApplied()) {
+ if (mActivityRecord.isLetterboxedForAspectRatioOnly()) {
return "ASPECT_RATIO";
}
return "UNKNOWN_REASON";
diff --git a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
index 3606a34..69be0d9 100644
--- a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
+++ b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.tracing.perfetto.DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT;
+import static android.tracing.perfetto.DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP;
import android.annotation.NonNull;
import android.internal.perfetto.protos.ShellTransitionOuterClass.ShellTransition;
@@ -44,7 +44,7 @@
DataSourceParams params =
new DataSourceParams.Builder()
.setBufferExhaustedPolicy(
- PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)
+ PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP)
.build();
mDataSource.register(params);
}
diff --git a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
index 1007357..b7944d3 100644
--- a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
@@ -107,11 +107,12 @@
void onConfigurationChanged() {
final Resources r = mContext.getResources();
- final int defaultThreshold = r.getDimensionPixelSize(
+ final int startThreshold = r.getDimensionPixelSize(
com.android.internal.R.dimen.system_gestures_start_threshold);
- mSwipeStartThreshold.set(defaultThreshold, defaultThreshold, defaultThreshold,
- defaultThreshold);
- mSwipeDistanceThreshold = defaultThreshold;
+ mSwipeStartThreshold.set(startThreshold, startThreshold, startThreshold,
+ startThreshold);
+ mSwipeDistanceThreshold = r.getDimensionPixelSize(
+ com.android.internal.R.dimen.system_gestures_distance_threshold);
final Display display = DisplayManagerGlobal.getInstance()
.getRealDisplay(Display.DEFAULT_DISPLAY);
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index f37638f..5c9a84d 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -40,8 +40,6 @@
import static com.android.server.wm.ActivityStarter.Request;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.LaunchParamsModifierUtils.applyLayoutGravity;
-import static com.android.server.wm.LaunchParamsModifierUtils.calculateLayoutBounds;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -644,13 +642,13 @@
displayArea.getStableRect(stableBounds);
if (windowLayout.hasSpecifiedSize()) {
- calculateLayoutBounds(stableBounds, windowLayout, inOutBounds,
+ LaunchParamsUtil.calculateLayoutBounds(stableBounds, windowLayout, inOutBounds,
/* desiredBounds */ null);
} else if (inOutBounds.isEmpty()) {
getTaskBounds(root, displayArea, windowLayout, WINDOWING_MODE_FREEFORM,
/* hasInitialBounds */ false, inOutBounds);
}
- applyLayoutGravity(verticalGravity, horizontalGravity, inOutBounds,
+ LaunchParamsUtil.applyLayoutGravity(verticalGravity, horizontalGravity, inOutBounds,
stableBounds);
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 2dc439d..0812323 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -113,10 +113,9 @@
private static final int LEGACY_STATE_READY = 1;
private static final int LEGACY_STATE_RUNNING = 2;
- private ITransitionPlayer mTransitionPlayer;
+ private final ArrayList<TransitionPlayerRecord> mTransitionPlayers = new ArrayList<>();
final TransitionMetricsReporter mTransitionMetricsReporter = new TransitionMetricsReporter();
- private WindowProcessController mTransitionPlayerProc;
final ActivityTaskManagerService mAtm;
BLASTSyncEngine mSyncEngine;
@@ -175,8 +174,6 @@
final Lock mRunningLock = new Lock();
- private final IBinder.DeathRecipient mTransitionPlayerDeath;
-
static class QueuedTransition {
final Transition mTransition;
final OnStartCollect mOnStartCollect;
@@ -243,11 +240,6 @@
TransitionController(ActivityTaskManagerService atm) {
mAtm = atm;
mRemotePlayer = new RemotePlayer(atm);
- mTransitionPlayerDeath = () -> {
- synchronized (mAtm.mGlobalLock) {
- detachPlayer();
- }
- };
}
void setWindowManager(WindowManagerService wms) {
@@ -266,11 +258,10 @@
mSyncEngine.addOnIdleListener(this::tryStartCollectFromQueue);
}
- @VisibleForTesting
- void detachPlayer() {
- if (mTransitionPlayer == null) return;
- // Immediately set to null so that nothing inadvertently starts/queues.
- mTransitionPlayer = null;
+ void flushRunningTransitions() {
+ // Temporarily clear so that nothing gets started/queued while flushing
+ final ArrayList<TransitionPlayerRecord> temp = new ArrayList<>(mTransitionPlayers);
+ mTransitionPlayers.clear();
// Clean-up/finish any playing transitions. Backwards since they can remove themselves.
for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
mPlayingTransitions.get(i).cleanUpOnFailure();
@@ -285,9 +276,10 @@
if (mCollectingTransition != null) {
mCollectingTransition.abort();
}
- mTransitionPlayerProc = null;
mRemotePlayer.clear();
mRunningLock.doNotifyLocked();
+ // Restore the rest of the player stack
+ mTransitionPlayers.addAll(temp);
}
/** @see #createTransition(int, int) */
@@ -302,7 +294,7 @@
@NonNull
Transition createTransition(@WindowManager.TransitionType int type,
@WindowManager.TransitionFlags int flags) {
- if (mTransitionPlayer == null) {
+ if (mTransitionPlayers.isEmpty()) {
throw new IllegalStateException("Shell Transitions not enabled");
}
if (mCollectingTransition != null) {
@@ -321,7 +313,7 @@
if (mCollectingTransition != null) {
throw new IllegalStateException("Simultaneous transition collection not supported.");
}
- if (mTransitionPlayer == null) {
+ if (mTransitionPlayers.isEmpty()) {
// If sysui has been killed (by a test) or crashed, we can temporarily have no player
// In this case, abort the transition.
transition.abort();
@@ -339,30 +331,56 @@
void registerTransitionPlayer(@Nullable ITransitionPlayer player,
@Nullable WindowProcessController playerProc) {
- try {
- // Note: asBinder() can be null if player is same process (likely in a test).
- if (mTransitionPlayer != null) {
- if (mTransitionPlayer.asBinder() != null) {
- mTransitionPlayer.asBinder().unlinkToDeath(mTransitionPlayerDeath, 0);
- }
- detachPlayer();
- }
- if (player.asBinder() != null) {
- player.asBinder().linkToDeath(mTransitionPlayerDeath, 0);
- }
- mTransitionPlayer = player;
- mTransitionPlayerProc = playerProc;
- } catch (RemoteException e) {
- throw new RuntimeException("Unable to set transition player");
+ if (!mTransitionPlayers.isEmpty()) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Registering transition "
+ + "player %s over %d other players", player.asBinder(),
+ mTransitionPlayers.size());
+ // flush currently running transitions so that the new player doesn't get
+ // intermediate state
+ flushRunningTransitions();
+ } else {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Registering transition "
+ + "player %s ", player.asBinder());
}
+ mTransitionPlayers.add(new TransitionPlayerRecord(player, playerProc));
+ }
+
+ @VisibleForTesting
+ void unregisterTransitionPlayer(@NonNull ITransitionPlayer player) {
+ int idx = mTransitionPlayers.size() - 1;
+ for (; idx >= 0; --idx) {
+ if (mTransitionPlayers.get(idx).mPlayer.asBinder() == player.asBinder()) break;
+ }
+ if (idx < 0) {
+ ProtoLog.w(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Attempt to unregister "
+ + "transition player %s but it isn't registered", player.asBinder());
+ return;
+ }
+ final boolean needsFlush = idx == (mTransitionPlayers.size() - 1);
+ final TransitionPlayerRecord record = mTransitionPlayers.remove(idx);
+ if (needsFlush) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Unregistering active "
+ + "transition player %s at index=%d leaving %d in stack", player.asBinder(),
+ idx, mTransitionPlayers.size());
+ } else {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Unregistering transition "
+ + "player %s at index=%d leaving %d in stack", player.asBinder(), idx,
+ mTransitionPlayers.size());
+ }
+ record.unlinkToDeath();
+ if (!needsFlush) {
+ // Not the active player, so no need to flush transitions.
+ return;
+ }
+ flushRunningTransitions();
}
@Nullable ITransitionPlayer getTransitionPlayer() {
- return mTransitionPlayer;
+ return mTransitionPlayers.isEmpty() ? null : mTransitionPlayers.getLast().mPlayer;
}
boolean isShellTransitionsEnabled() {
- return mTransitionPlayer != null;
+ return !mTransitionPlayers.isEmpty();
}
/** @return {@code true} if using shell-transitions rotation instead of fixed-rotation. */
@@ -677,7 +695,7 @@
Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type,
@WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger,
@NonNull WindowContainer readyGroupRef) {
- if (mTransitionPlayer == null) {
+ if (mTransitionPlayers.isEmpty()) {
return null;
}
Transition newTransition = null;
@@ -728,7 +746,7 @@
transition.getToken(), null));
return transition;
}
- if (mTransitionPlayer == null || transition.isAborted()) {
+ if (mTransitionPlayers.isEmpty() || transition.isAborted()) {
// Apparently, some tests will kill(and restart) systemui, so there is a chance that
// the player might be transiently null.
if (transition.isCollecting()) {
@@ -757,7 +775,8 @@
transition.mLogger.mRequestTimeNs = SystemClock.elapsedRealtimeNanos();
transition.mLogger.mRequest = request;
- mTransitionPlayer.requestStartTransition(transition.getToken(), request);
+ mTransitionPlayers.getLast().mPlayer.requestStartTransition(
+ transition.getToken(), request);
if (remoteTransition != null) {
transition.setRemoteAnimationApp(remoteTransition.getAppThread());
}
@@ -773,7 +792,7 @@
* @return the new transition if it was created for this request, `null` otherwise.
*/
Transition requestCloseTransitionIfNeeded(@NonNull WindowContainer<?> wc) {
- if (mTransitionPlayer == null || isCollecting()) return null;
+ if (mTransitionPlayers.isEmpty() || isCollecting()) return null;
if (!wc.isVisibleRequested()) return null;
return requestStartTransition(createTransition(TRANSIT_CLOSE, 0 /* flags */), wc.asTask(),
null /* remoteTransition */, null /* displayChange */);
@@ -1250,11 +1269,13 @@
/** Updates the process state of animation player. */
private void updateRunningRemoteAnimation(Transition transition, boolean isPlaying) {
- if (mTransitionPlayerProc == null) return;
+ if (mTransitionPlayers.isEmpty()) return;
+ final TransitionPlayerRecord record = mTransitionPlayers.getLast();
+ if (record.mPlayerProc == null) return;
if (isPlaying) {
- mTransitionPlayerProc.setRunningRemoteAnimation(true);
+ record.mPlayerProc.setRunningRemoteAnimation(true);
} else if (mPlayingTransitions.isEmpty()) {
- mTransitionPlayerProc.setRunningRemoteAnimation(false);
+ record.mPlayerProc.setRunningRemoteAnimation(false);
mRemotePlayer.clear();
}
}
@@ -1416,7 +1437,7 @@
*/
@NonNull
Transition createAndStartCollecting(int type) {
- if (mTransitionPlayer == null) {
+ if (mTransitionPlayers.isEmpty()) {
return null;
}
if (!mQueuedTransitions.isEmpty()) {
@@ -1477,6 +1498,40 @@
void onCollectStarted(boolean deferred);
}
+ private class TransitionPlayerRecord {
+ final ITransitionPlayer mPlayer;
+ IBinder.DeathRecipient mDeath = null;
+ private WindowProcessController mPlayerProc;
+
+ TransitionPlayerRecord(@NonNull ITransitionPlayer player,
+ @Nullable WindowProcessController playerProc) {
+ mPlayer = player;
+ mPlayerProc = playerProc;
+ try {
+ linkToDeath();
+ } catch (RemoteException e) {
+ throw new RuntimeException("Unable to set transition player");
+ }
+ }
+
+ private void linkToDeath() throws RemoteException {
+ // Note: asBinder() can be null if player is same process (likely in a test).
+ if (mPlayer.asBinder() == null) return;
+ mDeath = () -> {
+ synchronized (mAtm.mGlobalLock) {
+ unregisterTransitionPlayer(mPlayer);
+ }
+ };
+ mPlayer.asBinder().linkToDeath(mDeath, 0);
+ }
+
+ void unlinkToDeath() {
+ if (mPlayer.asBinder() == null || mDeath == null) return;
+ mPlayer.asBinder().unlinkToDeath(mDeath, 0);
+ mDeath = null;
+ }
+ }
+
/**
* This manages the animating state of processes that are running remote animations for
* {@link #mTransitionPlayerProc}.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b603551..0bf1c88 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8619,8 +8619,8 @@
return true;
}
// For a task session, find the activity identified by the launch cookie.
- final WindowContainerInfo wci = getTaskWindowContainerInfoForLaunchCookie(
- incomingSession.getTokenToRecord());
+ final WindowContainerInfo wci =
+ getTaskWindowContainerInfoForRecordingSession(incomingSession);
if (wci == null) {
Slog.w(TAG, "Handling a new recording session; unable to find the "
+ "WindowContainerToken");
@@ -9082,35 +9082,58 @@
}
/**
- * Retrieve the {@link WindowContainerInfo} of the task that contains the activity started with
- * the given launch cookie.
+ * Retrieve the {@link WindowContainerInfo} of the task that was launched for MediaProjection.
*
- * @param launchCookie the launch cookie set on the {@link ActivityOptions} when starting an
- * activity
+ * @param session the {@link ContentRecordingSession} containing the launch cookie and/or
+ * task id of the Task started for capture.
* @return a token representing the task containing the activity started with the given launch
* cookie, or {@code null} if the token couldn't be found.
*/
@VisibleForTesting
@Nullable
- WindowContainerInfo getTaskWindowContainerInfoForLaunchCookie(@NonNull IBinder launchCookie) {
- // Find the activity identified by the launch cookie.
- final ActivityRecord targetActivity =
- mRoot.getActivity(activity -> activity.mLaunchCookie == launchCookie);
- if (targetActivity == null) {
- Slog.w(TAG, "Unable to find the activity for this launch cookie");
- return null;
+ WindowContainerInfo getTaskWindowContainerInfoForRecordingSession(
+ @NonNull ContentRecordingSession session) {
+ WindowContainerToken taskWindowContainerToken = null;
+ ActivityRecord targetActivity = null;
+ Task targetTask = null;
+
+ // First attempt to find the launched task by looking for the launched activity with the
+ // matching launch cookie.
+ if (session.getTokenToRecord() != null) {
+ IBinder launchCookie = session.getTokenToRecord();
+ targetActivity = mRoot.getActivity(activity -> activity.mLaunchCookie == launchCookie);
+ if (targetActivity == null) {
+ Slog.w(TAG, "Unable to find the activity for this launch cookie");
+ } else {
+ if (targetActivity.getTask() == null) {
+ Slog.w(TAG, "Unable to find the task for this launch cookie");
+ } else {
+ targetTask = targetActivity.getTask();
+ taskWindowContainerToken = targetTask.mRemoteToken.toWindowContainerToken();
+ }
+ }
}
- if (targetActivity.getTask() == null) {
- Slog.w(TAG, "Unable to find the task for this launch cookie");
- return null;
+
+ // In the case we can't find an activity with a matching launch cookie, it could be due to
+ // the launched activity being closed, but the launched task is still open, so now attempt
+ // to look for the task directly.
+ if (taskWindowContainerToken == null && session.getTaskId() != -1) {
+ int targetTaskId = session.getTaskId();
+ targetTask = mRoot.getTask(task -> task.isTaskId(targetTaskId));
+ if (targetTask == null) {
+ Slog.w(TAG, "Unable to find the task for this projection");
+ } else {
+ taskWindowContainerToken = targetTask.mRemoteToken.toWindowContainerToken();
+ }
}
- WindowContainerToken taskWindowContainerToken =
- targetActivity.getTask().mRemoteToken.toWindowContainerToken();
+
+ // If we were unable to find the launched task in either fashion, then something must have
+ // wrong (i.e. the task was closed before capture started).
if (taskWindowContainerToken == null) {
- Slog.w(TAG, "Unable to find the WindowContainerToken for " + targetActivity.getName());
+ Slog.w(TAG, "Unable to find the WindowContainerToken for ContentRecordingSession");
return null;
}
- return new WindowContainerInfo(targetActivity.getUid(), taskWindowContainerToken);
+ return new WindowContainerInfo(targetTask.effectiveUid, taskWindowContainerToken);
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 99c4736..5e932d4 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -2088,6 +2088,19 @@
}
@Override
+ public void unregisterTransitionPlayer(ITransitionPlayer player) {
+ enforceTaskPermission("unregisterTransitionPlayer()");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ mTransitionController.unregisterTransitionPlayer(player);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public ITransitionMetricsReporter getTransitionMetricsReporter() {
return mTransitionController.mTransitionMetricsReporter;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index d733762..e763c9e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -16,6 +16,7 @@
package com.android.server.devicepolicy;
+import static android.app.admin.DevicePolicyIdentifiers.PACKAGES_SUSPENDED_POLICY;
import static android.app.admin.DevicePolicyIdentifiers.USER_CONTROL_DISABLED_PACKAGES_POLICY;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_TARGET_USER_ID;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_UPDATE_RESULT_KEY;
@@ -78,6 +79,10 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -260,9 +265,7 @@
boolean policyEnforced = Objects.equals(
localPolicyState.getCurrentResolvedPolicy(), value);
// TODO(b/285532044): remove hack and handle properly
- if (!policyEnforced
- && policyDefinition.getPolicyKey().getIdentifier().equals(
- USER_CONTROL_DISABLED_PACKAGES_POLICY)) {
+ if (!policyEnforced && shouldApplyPackageSetUnionPolicyHack(policyDefinition)) {
PolicyValue<Set<String>> parsedValue = (PolicyValue<Set<String>>) value;
PolicyValue<Set<String>> parsedResolvedValue =
(PolicyValue<Set<String>>) localPolicyState.getCurrentResolvedPolicy();
@@ -528,8 +531,7 @@
globalPolicyState.getCurrentResolvedPolicy(), value);
// TODO(b/285532044): remove hack and handle properly
if (!policyAppliedGlobally
- && policyDefinition.getPolicyKey().getIdentifier().equals(
- USER_CONTROL_DISABLED_PACKAGES_POLICY)) {
+ && shouldApplyPackageSetUnionPolicyHack(policyDefinition)) {
PolicyValue<Set<String>> parsedValue = (PolicyValue<Set<String>>) value;
PolicyValue<Set<String>> parsedResolvedValue =
(PolicyValue<Set<String>>) globalPolicyState.getCurrentResolvedPolicy();
@@ -666,8 +668,7 @@
}
// TODO(b/285532044): remove hack and handle properly
- if (policyDefinition.getPolicyKey().getIdentifier().equals(
- USER_CONTROL_DISABLED_PACKAGES_POLICY)) {
+ if (shouldApplyPackageSetUnionPolicyHack(policyDefinition)) {
if (!Objects.equals(value, localPolicyState.getCurrentResolvedPolicy())) {
PolicyValue<Set<String>> parsedValue = (PolicyValue<Set<String>>) value;
PolicyValue<Set<String>> parsedResolvedValue =
@@ -688,6 +689,12 @@
*/
@Nullable
<V> V getResolvedPolicy(@NonNull PolicyDefinition<V> policyDefinition, int userId) {
+ PolicyValue<V> resolvedValue = getResolvedPolicyValue(policyDefinition, userId);
+ return resolvedValue == null ? null : resolvedValue.getValue();
+ }
+
+ private <V> PolicyValue<V> getResolvedPolicyValue(@NonNull PolicyDefinition<V> policyDefinition,
+ int userId) {
Objects.requireNonNull(policyDefinition);
synchronized (mLock) {
@@ -699,11 +706,39 @@
resolvedValue = getGlobalPolicyStateLocked(
policyDefinition).getCurrentResolvedPolicy();
}
- return resolvedValue == null ? null : resolvedValue.getValue();
+ return resolvedValue;
}
}
/**
+ * Retrieves resolved policy for the provided {@code policyDefinition} and a list of
+ * users.
+ */
+ @Nullable
+ <V> V getResolvedPolicyAcrossUsers(@NonNull PolicyDefinition<V> policyDefinition,
+ List<Integer> users) {
+ Objects.requireNonNull(policyDefinition);
+
+ List<PolicyValue<V>> adminPolicies = new ArrayList<>();
+ synchronized (mLock) {
+ for (int userId : users) {
+ PolicyValue<V> resolvedValue = getResolvedPolicyValue(policyDefinition, userId);
+ if (resolvedValue != null) {
+ adminPolicies.add(resolvedValue);
+ }
+ }
+ }
+ // We will be aggregating PolicyValue across multiple admins across multiple users,
+ // including different policies set by the same admin on different users. This is
+ // not supported by ResolutionMechanism generically, instead we need to call the special
+ // resolve() method that doesn't care about admins who set the policy. Note that not every
+ // ResolutionMechanism supports this.
+ PolicyValue<V> resolvedValue =
+ policyDefinition.getResolutionMechanism().resolve(adminPolicies);
+ return resolvedValue == null ? null : resolvedValue.getValue();
+ }
+
+ /**
* Retrieves the policy set by the admin for the provided {@code policyDefinition} and
* {@code userId} if one was set, otherwise returns {@code null}.
*/
@@ -1743,6 +1778,18 @@
}
}
+ /**
+ * Create a backup of the policy engine XML file, so that we can recover previous state
+ * in case some data-loss bug is triggered e.g. during migration.
+ *
+ * Backup is only created if one with the same ID does not exist yet.
+ */
+ void createBackup(String backupId) {
+ synchronized (mLock) {
+ DevicePoliciesReaderWriter.createBackup(backupId);
+ }
+ }
+
<V> void reapplyAllPoliciesOnBootLocked() {
for (PolicyKey policy : mGlobalPolicies.keySet()) {
PolicyState<?> policyState = mGlobalPolicies.get(policy);
@@ -1820,8 +1867,22 @@
return false;
}
+ /**
+ * For PackageSetUnion policies, we can't simply compare the resolved policy against the admin's
+ * policy for equality to determine if the admin has applied the policy successfully, instead
+ * the admin's policy should be considered applied successfully as long as its policy is subset
+ * of the resolved policy. This method controls which policies should use this special logic.
+ */
+ private <V> boolean shouldApplyPackageSetUnionPolicyHack(PolicyDefinition<V> policy) {
+ String policyKey = policy.getPolicyKey().getIdentifier();
+ return policyKey.equals(USER_CONTROL_DISABLED_PACKAGES_POLICY)
+ || policyKey.equals(PACKAGES_SUSPENDED_POLICY);
+ }
+
private class DevicePoliciesReaderWriter {
private static final String DEVICE_POLICIES_XML = "device_policy_state.xml";
+ private static final String BACKUP_DIRECTORY = "device_policy_backups";
+ private static final String BACKUP_FILENAME = "device_policy_state.%s.xml";
private static final String TAG_LOCAL_POLICY_ENTRY = "local-policy-entry";
private static final String TAG_GLOBAL_POLICY_ENTRY = "global-policy-entry";
private static final String TAG_POLICY_STATE_ENTRY = "policy-state-entry";
@@ -1836,8 +1897,30 @@
private final File mFile;
+ private static File getFileName() {
+ return new File(Environment.getDataSystemDirectory(), DEVICE_POLICIES_XML);
+ }
private DevicePoliciesReaderWriter() {
- mFile = new File(Environment.getDataSystemDirectory(), DEVICE_POLICIES_XML);
+ mFile = getFileName();
+ }
+
+ public static void createBackup(String backupId) {
+ try {
+ File backupDirectory = new File(Environment.getDataSystemDirectory(),
+ BACKUP_DIRECTORY);
+ backupDirectory.mkdir();
+ Path backupPath = Path.of(backupDirectory.getPath(),
+ BACKUP_FILENAME.formatted(backupId));
+ if (backupPath.toFile().exists()) {
+ Log.w(TAG, "Backup already exist: " + backupPath);
+ } else {
+ Files.copy(getFileName().toPath(), backupPath,
+ StandardCopyOption.REPLACE_EXISTING);
+ Log.i(TAG, "Backup created at " + backupPath);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Cannot create backup " + backupId, e);
+ }
}
void writeToFileLocked() {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 5bf5efd..bdd0730 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -569,6 +569,7 @@
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -2221,32 +2222,6 @@
return packageNameAndSignature;
}
- private void unsuspendAppsForQuietProfiles() {
- PackageManagerInternal pmi = mInjector.getPackageManagerInternal();
- List<UserInfo> users = mUserManagerInternal.getUsers(true /* excludeDying */);
-
- for (UserInfo user : users) {
- if (!user.isManagedProfile() || !user.isQuietModeEnabled()) {
- continue;
- }
- int userId = user.id;
- var suspendedByAdmin = getPackagesSuspendedByAdmin(userId);
- var packagesToUnsuspend = mInjector.getPackageManager(userId)
- .getInstalledPackages(PackageManager.PackageInfoFlags.of(
- MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE))
- .stream()
- .map(packageInfo -> packageInfo.packageName)
- .filter(pkg -> !suspendedByAdmin.contains(pkg))
- .toArray(String[]::new);
-
- Slogf.i(LOG_TAG, "Unsuspending work apps for user %d", userId);
- // When app suspension was used for quiet mode, the apps were suspended by platform
- // package, just like when admin suspends them. So although it wasn't admin who
- // suspended, this method will remove the right suspension record.
- pmi.setPackagesSuspendedByAdmin(userId, packagesToUnsuspend, false /* suspended */);
- }
- }
-
private Owners makeOwners(Injector injector, PolicyPathProvider pathProvider) {
return new Owners(
injector.getUserManager(), injector.getUserManagerInternal(),
@@ -3470,6 +3445,79 @@
new BooleanPolicyValue(true));
}
+ @GuardedBy("getLockObject()")
+ private boolean maybeMigrateRequiredPasswordComplexityLocked(String backupId) {
+ Slog.i(LOG_TAG, "Migrating password complexity to policy engine");
+ if (!Flags.unmanagedModeMigration()) {
+ return false;
+ }
+ if (mOwners.isRequiredPasswordComplexityMigrated()) {
+ return false;
+ }
+ // Create backup if none exists
+ mDevicePolicyEngine.createBackup(backupId);
+ try {
+ iterateThroughDpcAdminsLocked((admin, enforcingAdmin) -> {
+ int userId = enforcingAdmin.getUserId();
+ if (admin.mPasswordComplexity != PASSWORD_COMPLEXITY_NONE) {
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.PASSWORD_COMPLEXITY,
+ enforcingAdmin,
+ new IntegerPolicyValue(admin.mPasswordComplexity),
+ userId);
+ }
+ ActiveAdmin parentAdmin = admin.getParentActiveAdmin();
+ if (parentAdmin != null
+ && parentAdmin.mPasswordComplexity != PASSWORD_COMPLEXITY_NONE) {
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.PASSWORD_COMPLEXITY,
+ enforcingAdmin,
+ new IntegerPolicyValue(parentAdmin.mPasswordComplexity),
+ getProfileParentId(userId));
+ }
+ });
+ } catch (Exception e) {
+ Slog.wtf(LOG_TAG, "Failed to migrate password complexity to policy engine", e);
+ }
+
+ Slog.i(LOG_TAG, "Marking password complexity migration complete");
+ mOwners.markRequiredPasswordComplexityMigrated();
+ return true;
+ }
+
+ @GuardedBy("getLockObject()")
+ private boolean maybeMigrateSuspendedPackagesLocked(String backupId) {
+ Slog.i(LOG_TAG, "Migrating suspended packages to policy engine");
+ if (!Flags.unmanagedModeMigration()) {
+ return false;
+ }
+ if (mOwners.isSuspendedPackagesMigrated()) {
+ return false;
+ }
+ // Create backup if none exists
+ mDevicePolicyEngine.createBackup(backupId);
+ try {
+ iterateThroughDpcAdminsLocked((admin, enforcingAdmin) -> {
+ if (admin.suspendedPackages == null || admin.suspendedPackages.size() == 0) {
+ return;
+ }
+ int userId = enforcingAdmin.getUserId();
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.PACKAGES_SUSPENDED,
+ enforcingAdmin,
+ new PackageSetPolicyValue(new ArraySet<>(admin.suspendedPackages)),
+ userId);
+ });
+ } catch (Exception e) {
+ Slog.wtf(LOG_TAG, "Failed to migrate suspended packages to policy engine", e);
+ }
+
+ Slog.i(LOG_TAG, "Marking suspended packages migration complete");
+ mOwners.markSuspendedPackagesMigrated();
+ return true;
+ }
+
+
private void applyManagedSubscriptionsPolicyIfRequired() {
int copeProfileUserId = getOrganizationOwnedProfileUserId();
// This policy is relevant only for COPE devices.
@@ -4254,27 +4302,50 @@
}
final int userId = caller.getUserId();
+ EnforcingAdmin enforcingAdmin = null;
synchronized (getLockObject()) {
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
+ if (Flags.unmanagedModeMigration()) {
+ enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who,
+ userId,
+ getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD));
+ }
// If setPasswordQuality is called on the parent, ensure that
// the primary admin does not have password complexity state (this is an
// unsupported state).
if (parent) {
- final ActiveAdmin primaryAdmin = getActiveAdminForCallerLocked(
- who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, false);
- final boolean hasComplexitySet =
- primaryAdmin.mPasswordComplexity != PASSWORD_COMPLEXITY_NONE;
+ final boolean hasComplexitySet;
+ if (Flags.unmanagedModeMigration()) {
+ Integer complexity = mDevicePolicyEngine.getLocalPolicySetByAdmin(
+ PolicyDefinition.PASSWORD_COMPLEXITY,
+ enforcingAdmin,
+ userId);
+ hasComplexitySet = complexity != null && complexity != PASSWORD_COMPLEXITY_NONE;
+ } else {
+ final ActiveAdmin primaryAdmin = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, false);
+ hasComplexitySet = primaryAdmin.mPasswordComplexity != PASSWORD_COMPLEXITY_NONE;
+ }
+
Preconditions.checkState(!hasComplexitySet,
"Cannot set password quality when complexity is set on the primary admin."
+ " Set the primary admin's complexity to NONE first.");
}
+ final EnforcingAdmin enforcingAdminFinal = enforcingAdmin;
mInjector.binderWithCleanCallingIdentity(() -> {
final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
if (passwordPolicy.quality != quality) {
passwordPolicy.quality = quality;
- ap.mPasswordComplexity = PASSWORD_COMPLEXITY_NONE;
+ if (Flags.unmanagedModeMigration()) {
+ int affectedUser = parent ? getProfileParentId(userId) : userId;
+ mDevicePolicyEngine.removeLocalPolicy(PolicyDefinition.PASSWORD_COMPLEXITY,
+ enforcingAdminFinal, affectedUser);
+ } else {
+ ap.mPasswordComplexity = PASSWORD_COMPLEXITY_NONE;
+ }
resetInactivePasswordRequirementsIfRPlus(userId, ap);
updatePasswordValidityCheckpointLocked(userId, parent);
updatePasswordQualityCacheForUserGroup(userId);
@@ -4405,6 +4476,10 @@
}
}
+ /**
+ * @deprecated use {@link #getResolvedLockscreenPolicy(PolicyDefinition, int)} for
+ * coexistable policies
+ */
@GuardedBy("getLockObject()")
private List<ActiveAdmin> getActiveAdminsForLockscreenPoliciesLocked(int userHandle) {
if (isSeparateProfileChallengeEnabled(userHandle)) {
@@ -4428,6 +4503,25 @@
}
/**
+ * Returns a user's resolved lockscreen policy from all admins. This is different from normal
+ * policy resolution because if the specified user has a work profile with unified challenge,
+ * all policies set on the profile will also affect that user.
+ */
+ private <V> V getResolvedLockscreenPolicy(PolicyDefinition<V> policyDefinition, int userId) {
+ if (isSeparateProfileChallengeEnabled(userId)) {
+ // If this profile has a separate challenge, only return policies targeting itself.
+ return mDevicePolicyEngine.getResolvedPolicy(policyDefinition, userId);
+ }
+ // Otherwise, this user is either a full user, or it's a profile with unified challenge.
+ // In both cases we query the parent user who owns the credential (the parent user of a full
+ // user is itself), plus any profile of the parent user who has unified challenge since
+ // the policy of a unified challenge profile is enforced on the parent.
+ return getResolvedPolicyForUserAndItsManagedProfiles(policyDefinition,
+ getProfileParentId(userId),
+ (user) -> mLockPatternUtils.isProfileWithUnifiedChallenge(user.id));
+
+ }
+ /**
* Get the list of active admins for an affected user:
* <ul>
* <li>The active admins associated with the userHandle itself</li>
@@ -4460,6 +4554,9 @@
* profile associated with the given user. Optionally also include the admin of each managed
* profile.
* <p> Should not be called on a profile user.
+ *
+ * For coexistable policy, please use
+ * {@link #getResolvedPolicyForUserAndItsManagedProfiles(PolicyDefinition, int, Predicate)}
*/
@GuardedBy("getLockObject()")
private List<ActiveAdmin> getActiveAdminsForUserAndItsManagedProfilesLocked(int userHandle,
@@ -4486,6 +4583,23 @@
return admins;
}
+ private <V> V getResolvedPolicyForUserAndItsManagedProfiles(
+ PolicyDefinition<V> policyDefinition, int userHandle,
+ Predicate<UserInfo> shouldIncludeProfile) {
+ List<Integer> users = new ArrayList<>();
+
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) {
+ if (userInfo.id == userHandle) {
+ users.add(userInfo.id);
+ } else if (userInfo.isManagedProfile() && shouldIncludeProfile.test(userInfo)) {
+ users.add(userInfo.id);
+ }
+ }
+ });
+ return mDevicePolicyEngine.getResolvedPolicyAcrossUsers(policyDefinition, users);
+ }
+
/**
* Returns the list of admins on the given user, as well as parent admins for each managed
* profile associated with the given user. Optionally also include the admin of each managed
@@ -5266,13 +5380,30 @@
/* shouldIncludeProfileAdmins */ (user) -> user.id == profileUser
|| !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id));
ArrayList<PasswordMetrics> adminMetrics = new ArrayList<>(admins.size());
- int maxRequiredComplexity = PASSWORD_COMPLEXITY_NONE;
- for (ActiveAdmin admin : admins) {
- adminMetrics.add(admin.mPasswordPolicy.getMinMetrics());
- maxRequiredComplexity = Math.max(maxRequiredComplexity, admin.mPasswordComplexity);
+ if (Flags.unmanagedModeMigration()) {
+ for (ActiveAdmin admin: admins) {
+ adminMetrics.add(admin.mPasswordPolicy.getMinMetrics());
+ }
+ Integer maxRequiredComplexity = getResolvedPolicyForUserAndItsManagedProfiles(
+ PolicyDefinition.PASSWORD_COMPLEXITY,
+ userHandle,
+ /* shouldIncludeProfileAdmins */ (user) -> user.id == profileUser
+ || !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id));
+ return PasswordMetrics.validatePasswordMetrics(
+ PasswordMetrics.merge(adminMetrics),
+ maxRequiredComplexity != null
+ ? maxRequiredComplexity : PASSWORD_COMPLEXITY_NONE,
+ metrics).isEmpty();
+ } else {
+ int maxRequiredComplexity = PASSWORD_COMPLEXITY_NONE;
+ for (ActiveAdmin admin : admins) {
+ adminMetrics.add(admin.mPasswordPolicy.getMinMetrics());
+ maxRequiredComplexity = Math.max(maxRequiredComplexity,
+ admin.mPasswordComplexity);
+ }
+ return PasswordMetrics.validatePasswordMetrics(PasswordMetrics.merge(adminMetrics),
+ maxRequiredComplexity, metrics).isEmpty();
}
- return PasswordMetrics.validatePasswordMetrics(PasswordMetrics.merge(adminMetrics),
- maxRequiredComplexity, metrics).isEmpty();
}
}
@@ -5363,6 +5494,76 @@
Preconditions.checkArgument(allowedModes.contains(passwordComplexity),
"Provided complexity is not one of the allowed values.");
+ if (!Flags.unmanagedModeMigration()) {
+ setRequiredPasswordComplexityPreCoexistence(callerPackageName, passwordComplexity,
+ calledOnParent);
+ return;
+ }
+
+ CallerIdentity caller = getCallerIdentity(callerPackageName);
+ int affectedUser = calledOnParent
+ ? getProfileParentId(caller.getUserId()) : caller.getUserId();
+ EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
+ MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, caller.getPackageName(),
+ caller.getUserId());
+ Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
+
+ ActiveAdmin activeAdmin = admin.getActiveAdmin();
+
+ // We require the caller to explicitly clear any password quality requirements set
+ // on the parent DPM instance, to avoid the case where password requirements are
+ // specified in the form of quality on the parent but complexity on the profile
+ // itself.
+ if (!calledOnParent) {
+ final boolean hasQualityRequirementsOnParent = activeAdmin.hasParentActiveAdmin()
+ && activeAdmin.getParentActiveAdmin().mPasswordPolicy.quality
+ != PASSWORD_QUALITY_UNSPECIFIED;
+ Preconditions.checkState(!hasQualityRequirementsOnParent,
+ "Password quality is set on the parent when attempting to set password"
+ + "complexity. Clear the quality by setting the password quality "
+ + "on the parent to PASSWORD_QUALITY_UNSPECIFIED first");
+ }
+
+ if (passwordComplexity != PASSWORD_COMPLEXITY_NONE) {
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.PASSWORD_COMPLEXITY,
+ admin,
+ new IntegerPolicyValue(passwordComplexity),
+ affectedUser);
+ } else {
+ mDevicePolicyEngine.removeLocalPolicy(
+ PolicyDefinition.PASSWORD_COMPLEXITY,
+ admin,
+ affectedUser);
+ }
+
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ // Reset the password policy.
+ if (calledOnParent) {
+ activeAdmin.getParentActiveAdmin().mPasswordPolicy = new PasswordPolicy();
+ } else {
+ activeAdmin.mPasswordPolicy = new PasswordPolicy();
+ }
+ synchronized (getLockObject()) {
+ updatePasswordValidityCheckpointLocked(caller.getUserId(), calledOnParent);
+ }
+ updatePasswordQualityCacheForUserGroup(caller.getUserId());
+ saveSettingsLocked(caller.getUserId());
+ });
+
+
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_PASSWORD_COMPLEXITY)
+ .setAdmin(caller.getPackageName())
+ .setInt(passwordComplexity)
+ .setBoolean(calledOnParent)
+ .write();
+ logPasswordComplexityRequiredIfSecurityLogEnabled(caller.getPackageName(),
+ caller.getUserId(), calledOnParent, passwordComplexity);
+ }
+
+ private void setRequiredPasswordComplexityPreCoexistence(
+ String callerPackageName, int passwordComplexity, boolean calledOnParent) {
CallerIdentity caller = getCallerIdentity(callerPackageName);
if (!isPermissionCheckFlagEnabled()) {
Preconditions.checkCallAuthorization(
@@ -5441,6 +5642,30 @@
@GuardedBy("getLockObject()")
private int getAggregatedPasswordComplexityLocked(@UserIdInt int userHandle,
boolean deviceWideOnly) {
+ if (Flags.unmanagedModeMigration()) {
+ return getAggregatedPasswordComplexity(userHandle, deviceWideOnly);
+ } else {
+ return getAggregatedPasswordComplexityPreCoexistenceLocked(userHandle, deviceWideOnly);
+ }
+ }
+
+ private int getAggregatedPasswordComplexity(@UserIdInt int userHandle, boolean deviceWideOnly) {
+ ensureLocked();
+ Integer result;
+ if (deviceWideOnly) {
+ result = getResolvedPolicyForUserAndItsManagedProfiles(
+ PolicyDefinition.PASSWORD_COMPLEXITY,
+ userHandle,
+ /* shouldIncludeProfile */ (user) -> false);
+ } else {
+ result = getResolvedLockscreenPolicy(PolicyDefinition.PASSWORD_COMPLEXITY, userHandle);
+ }
+ return result != null ? result : PASSWORD_COMPLEXITY_NONE;
+ }
+
+ @GuardedBy("getLockObject()")
+ private int getAggregatedPasswordComplexityPreCoexistenceLocked(@UserIdInt int userHandle,
+ boolean deviceWideOnly) {
ensureLocked();
final List<ActiveAdmin> admins;
if (deviceWideOnly) {
@@ -5462,24 +5687,30 @@
return PASSWORD_COMPLEXITY_NONE;
}
- final CallerIdentity caller = getCallerIdentity();
-
- if (isPermissionCheckFlagEnabled()) {
+ if (Flags.unmanagedModeMigration()) {
+ final CallerIdentity caller = getCallerIdentity(callerPackageName);
int affectedUser = calledOnParent ? getProfileParentId(caller.getUserId())
: caller.getUserId();
enforcePermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
callerPackageName, affectedUser);
+
+ Integer complexity = mDevicePolicyEngine.getResolvedPolicy(
+ PolicyDefinition.PASSWORD_COMPLEXITY,
+ affectedUser);
+ return complexity != null ? complexity : PASSWORD_COMPLEXITY_NONE;
} else {
+ final CallerIdentity caller = getCallerIdentity();
+
Preconditions.checkCallAuthorization(
isDefaultDeviceOwner(caller) || isProfileOwner(caller));
Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
- }
- synchronized (getLockObject()) {
- final ActiveAdmin requiredAdmin = getParentOfAdminIfRequired(
- getDeviceOrProfileOwnerAdminLocked(caller.getUserId()), calledOnParent);
- return requiredAdmin.mPasswordComplexity;
+ synchronized (getLockObject()) {
+ final ActiveAdmin requiredAdmin = getParentOfAdminIfRequired(
+ getDeviceOrProfileOwnerAdminLocked(caller.getUserId()), calledOnParent);
+ return requiredAdmin.mPasswordComplexity;
+ }
}
}
@@ -9288,6 +9519,10 @@
}
}
+ // TODO: with a quick glance this logic seems incomplete that it doesn't properly handle
+ // the different behaviour between a profile with separate challenge vs a profile with
+ // unified challenge, which was part of getActiveAdminsForLockscreenPoliciesLocked()
+ // before the migration.
if (isUnicornFlagEnabled()) {
Integer features = mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
@@ -12983,8 +13218,7 @@
return result;
}
- @Override
- public String[] setPackagesSuspended(ComponentName who, String callerPackage,
+ private String[] setPackagesSuspendedPreCoexistence(ComponentName who, String callerPackage,
String[] packageNames, boolean suspended) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
ActiveAdmin admin;
@@ -13065,6 +13299,78 @@
return result;
}
+ @Override
+ public String[] setPackagesSuspended(ComponentName who, String callerPackage,
+ String[] packageNames, boolean suspended) {
+ if (!Flags.unmanagedModeMigration()) {
+ return setPackagesSuspendedPreCoexistence(who, callerPackage, packageNames, suspended);
+ }
+
+ final CallerIdentity caller = getCallerIdentity(who, callerPackage);
+
+ EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+ who,
+ MANAGE_DEVICE_POLICY_PACKAGE_STATE,
+ caller.getPackageName(),
+ caller.getUserId());
+ checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PACKAGES_SUSPENDED);
+
+ Set<String> packages = new ArraySet<>(packageNames);
+ Set<String> suspendedPackagesBefore = mDevicePolicyEngine.getResolvedPolicy(
+ PolicyDefinition.PACKAGES_SUSPENDED, caller.getUserId());
+
+ Set<String> currentPackages = mDevicePolicyEngine.getLocalPolicySetByAdmin(
+ PolicyDefinition.PACKAGES_SUSPENDED,
+ enforcingAdmin,
+ caller.getUserId());
+ if (currentPackages == null) currentPackages = new ArraySet<>();
+ if (suspended) {
+ currentPackages.addAll(packages);
+ } else {
+ currentPackages.removeAll(packages);
+ }
+ if (currentPackages.isEmpty()) {
+ mDevicePolicyEngine.removeLocalPolicy(
+ PolicyDefinition.PACKAGES_SUSPENDED,
+ enforcingAdmin,
+ caller.getUserId());
+ } else {
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.PACKAGES_SUSPENDED,
+ enforcingAdmin,
+ new PackageSetPolicyValue(currentPackages),
+ caller.getUserId());
+ }
+
+ Set<String> suspendedPackagesAfter = mDevicePolicyEngine.getResolvedPolicy(
+ PolicyDefinition.PACKAGES_SUSPENDED, caller.getUserId());
+
+ PackageSuspender suspender = new PackageSuspender(
+ suspendedPackagesBefore, suspendedPackagesAfter,
+ listPolicyExemptAppsUnchecked(mContext),
+ mInjector.getPackageManagerInternal(), caller.getUserId());
+
+ String[] result;
+ synchronized (getLockObject()) {
+ long id = mInjector.binderClearCallingIdentity();
+ try {
+ result = suspended ? suspender.suspend(packages) : suspender.unsuspend(packages);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
+ }
+ }
+
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_PACKAGES_SUSPENDED)
+ .setAdmin(caller.getPackageName())
+ .setBoolean(/* isDelegate */ who == null)
+ .setStrings(packageNames)
+ .write();
+
+ if (VERBOSE_LOG) Slogf.v(LOG_TAG, "Returning %s", Arrays.toString(result));
+ return result;
+ }
+
/**
* Returns an array containing the union of the given non-suspended packages and
* exempt apps. Assumes both parameters are non-null and non-empty.
@@ -13086,7 +13392,7 @@
public boolean isPackageSuspended(ComponentName who, String callerPackage, String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- if (isUnicornFlagEnabled()) {
+ if (Flags.unmanagedModeMigration()) {
enforcePermission(
MANAGE_DEVICE_POLICY_PACKAGE_STATE,
caller.getPackageName(),
@@ -15257,17 +15563,6 @@
}
}
- private Set<String> getPackagesSuspendedByAdmin(@UserIdInt int userId) {
- synchronized (getLockObject()) {
- ActiveAdmin admin = getDeviceOrProfileOwnerAdminLocked(userId);
- if (admin == null || admin.suspendedPackages == null) {
- return Collections.emptySet();
- } else {
- return new ArraySet<>(admin.suspendedPackages);
- }
- }
- }
-
/**
* We need to update the internal state of whether a user has completed setup or a
* device has paired once. After that, we ignore any changes that reset the
@@ -23523,7 +23818,39 @@
}
Slog.w(LOG_TAG, "Work apps may have been paused via suspension previously.");
- unsuspendAppsForQuietProfiles();
+ PackageManagerInternal pmi = mInjector.getPackageManagerInternal();
+ List<UserInfo> users = mUserManagerInternal.getUsers(true /* excludeDying */);
+
+ for (UserInfo user : users) {
+ if (!user.isManagedProfile() || !user.isQuietModeEnabled()) {
+ continue;
+ }
+ int userId = user.id;
+ Set<String> suspendedByAdmin;
+ synchronized (getLockObject()) {
+ ActiveAdmin admin = getDeviceOrProfileOwnerAdminLocked(userId);
+ // This is legacy code from Turn off Work 2.0 which is before setPackagesSuspended
+ // is migrated to PolicyEngine, so we only need to query the legacy ActiveAdmin here
+ if (admin == null || admin.suspendedPackages == null) {
+ suspendedByAdmin = Collections.emptySet();
+ } else {
+ suspendedByAdmin = new ArraySet<>(admin.suspendedPackages);
+ }
+ }
+ var packagesToUnsuspend = mInjector.getPackageManager(userId)
+ .getInstalledPackages(PackageManager.PackageInfoFlags.of(
+ MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE))
+ .stream()
+ .map(packageInfo -> packageInfo.packageName)
+ .filter(pkg -> !suspendedByAdmin.contains(pkg))
+ .toArray(String[]::new);
+
+ Slogf.i(LOG_TAG, "Unsuspending work apps for user %d", userId);
+ // When app suspension was used for quiet mode, the apps were suspended by platform
+ // package, just like when admin suspends them. So although it wasn't admin who
+ // suspended, this method will remove the right suspension record.
+ pmi.setPackagesSuspendedByAdmin(userId, packagesToUnsuspend, false /* suspended */);
+ }
}
public void setMtePolicy(int flags, String callerPackageName) {
@@ -23947,6 +24274,15 @@
@GuardedBy("getLockObject()")
private void migratePoliciesToPolicyEngineLocked() {
maybeMigrateSecurityLoggingPolicyLocked();
+ // ID format: <sdk-int>.<auto_increment_id>.<descriptions>'
+ String unmanagedBackupId = "35.1.unmanaged-mode";
+ boolean migrated = false;
+ migrated = migrated | maybeMigrateRequiredPasswordComplexityLocked(unmanagedBackupId);
+ migrated = migrated | maybeMigrateSuspendedPackagesLocked(unmanagedBackupId);
+ if (migrated) {
+ Slogf.i(LOG_TAG, "Backup made: " + unmanagedBackupId);
+ }
+ // Additional migration steps should repeat the pattern above with a new backupId.
}
private void migrateAutoTimezonePolicy() {
@@ -24211,6 +24547,23 @@
});
}
+ @GuardedBy("getLockObject()")
+ private void iterateThroughDpcAdminsLocked(BiConsumer<ActiveAdmin, EnforcingAdmin> runner) {
+ Binder.withCleanCallingIdentity(() -> {
+ List<UserInfo> users = mUserManager.getUsers();
+ for (UserInfo userInfo : users) {
+ ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
+ if (admin == null) continue;
+ EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ admin.info.getComponent(),
+ userInfo.id,
+ admin);
+
+ runner.accept(admin, enforcingAdmin);
+ }
+ });
+ }
+
private List<PackageInfo> getInstalledPackagesOnUser(int userId) {
return mInjector.binderWithCleanCallingIdentity(() ->
mContext.getPackageManager().getInstalledPackagesAsUser(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/MostRestrictive.java b/services/devicepolicy/java/com/android/server/devicepolicy/MostRestrictive.java
index 7e8eaa7..2066376 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/MostRestrictive.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/MostRestrictive.java
@@ -19,9 +19,9 @@
import android.annotation.NonNull;
import android.app.admin.PolicyValue;
+import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
final class MostRestrictive<V> extends ResolutionMechanism<V> {
@@ -33,18 +33,21 @@
@Override
PolicyValue<V> resolve(@NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<V>> adminPolicies) {
+ return resolve(new ArrayList<>(adminPolicies.values()));
+ }
+
+ @Override
+ PolicyValue<V> resolve(List<PolicyValue<V>> adminPolicies) {
if (adminPolicies.isEmpty()) {
return null;
}
for (PolicyValue<V> value : mMostToLeastRestrictive) {
- if (adminPolicies.containsValue(value)) {
+ if (adminPolicies.contains(value)) {
return value;
}
}
// Return first set policy if none can be found in known values
- Map.Entry<EnforcingAdmin, PolicyValue<V>> policy =
- adminPolicies.entrySet().stream().findFirst().get();
- return policy.getValue();
+ return adminPolicies.get(0);
}
@Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 7912cbc..3f9605a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -636,6 +636,33 @@
}
}
+ boolean isRequiredPasswordComplexityMigrated() {
+ synchronized (mData) {
+ return mData.mRequiredPasswordComplexityMigrated;
+ }
+ }
+
+ void markRequiredPasswordComplexityMigrated() {
+ synchronized (mData) {
+ mData.mRequiredPasswordComplexityMigrated = true;
+ mData.writeDeviceOwner();
+ }
+
+ }
+
+ boolean isSuspendedPackagesMigrated() {
+ synchronized (mData) {
+ return mData.mSuspendedPackagesMigrated;
+ }
+ }
+
+ void markSuspendedPackagesMigrated() {
+ synchronized (mData) {
+ mData.mSuspendedPackagesMigrated = true;
+ mData.writeDeviceOwner();
+ }
+ }
+
boolean isMigratedPostUpdate() {
synchronized (mData) {
return mData.mPoliciesMigratedPostUpdate;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index d02cfee..2ea5f16 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -88,7 +88,9 @@
private static final String ATTR_MIGRATED_TO_POLICY_ENGINE = "migratedToPolicyEngine";
private static final String ATTR_SECURITY_LOG_MIGRATED = "securityLogMigrated";
-
+ private static final String ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED =
+ "passwordComplexityMigrated";
+ private static final String ATTR_SUSPENDED_PACKAGES_MIGRATED = "suspendedPackagesMigrated";
private static final String ATTR_MIGRATED_POST_UPGRADE = "migratedPostUpgrade";
// Internal state for the device owner package.
@@ -118,6 +120,8 @@
boolean mMigratedToPolicyEngine = false;
boolean mSecurityLoggingMigrated = false;
+ boolean mRequiredPasswordComplexityMigrated = false;
+ boolean mSuspendedPackagesMigrated = false;
boolean mPoliciesMigratedPostUpdate = false;
@@ -409,6 +413,14 @@
if (Flags.securityLogV2Enabled()) {
out.attributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, mSecurityLoggingMigrated);
}
+ if (Flags.unmanagedModeMigration()) {
+ out.attributeBoolean(null, ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED,
+ mRequiredPasswordComplexityMigrated);
+ out.attributeBoolean(null, ATTR_SUSPENDED_PACKAGES_MIGRATED,
+ mSuspendedPackagesMigrated);
+
+ }
+
out.endTag(null, TAG_POLICY_ENGINE_MIGRATION);
}
@@ -473,6 +485,12 @@
null, ATTR_MIGRATED_POST_UPGRADE, false);
mSecurityLoggingMigrated = Flags.securityLogV2Enabled()
&& parser.getAttributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, false);
+ mRequiredPasswordComplexityMigrated = Flags.unmanagedModeMigration()
+ && parser.getAttributeBoolean(null,
+ ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED, false);
+ mSuspendedPackagesMigrated = Flags.unmanagedModeMigration()
+ && parser.getAttributeBoolean(null,
+ ATTR_SUSPENDED_PACKAGES_MIGRATED, false);
break;
default:
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java
new file mode 100644
index 0000000..40cf0e9
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java
@@ -0,0 +1,155 @@
+/*
+ * 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.devicepolicy;
+
+import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG;
+
+import android.annotation.Nullable;
+import android.content.pm.PackageManagerInternal;
+import android.util.ArraySet;
+
+import com.android.server.utils.Slogf;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Helper class for calling into PackageManagerInternal.setPackagesSuspendedByAdmin.
+ * Two main things this class encapsulates:
+ * 1. Handling of the DPM internal suspension exemption list
+ * 2. Calculating the failed packages result in the context of coexistence
+ *
+ * 1 is handled by the two internal methods {@link #suspendWithExemption(Set)} and
+ * {@link #unsuspendWithExemption(Set)} where the exemption list is taken into consideration
+ * before and after calling {@link PackageManagerInternal#setPackagesSuspendedByAdmin}.
+ * In order to compute 2, the resolved package suspension state before and after suspension is
+ * needed as multiple admins can both suspend the same packages under coexistence.
+ */
+public class PackageSuspender {
+
+ private final Set<String> mSuspendedPackageBefore;
+ private final Set<String> mSuspendedPackageAfter;
+ private final List<String> mExemptedPackages;
+ private final PackageManagerInternal mPackageManager;
+ private final int mUserId;
+
+ public PackageSuspender(@Nullable Set<String> suspendedPackageBefore,
+ @Nullable Set<String> suspendedPackageAfter, List<String> exemptedPackages,
+ PackageManagerInternal pmi, int userId) {
+ mSuspendedPackageBefore =
+ suspendedPackageBefore != null ? suspendedPackageBefore : Collections.emptySet();
+ mSuspendedPackageAfter =
+ suspendedPackageAfter != null ? suspendedPackageAfter : Collections.emptySet();
+ mExemptedPackages = exemptedPackages;
+ mPackageManager = pmi;
+ mUserId = userId;
+ }
+
+ /**
+ * Suspend packages that are requested by a single admin
+ *
+ * @return a list of packages that the admin has requested to suspend but could not be
+ * suspended, due to DPM and PackageManager exemption list.
+ *
+ */
+ public String[] suspend(Set<String> packages) {
+ // When suspending, call PM with the list of packages admin has requested, even if some
+ // of these packages are already in suspension (some other admin might have already
+ // suspended them). We do it this way so that we can simply return the failed list from
+ // PackageManager to the caller as the accurate list of unsuspended packages.
+ // This is different from the unsuspend() logic, please see below.
+ //
+ // For example admin A already suspended package 1, 2 and 3, but package 3 is
+ // PackageManager-exempted. Now admin B wants to suspend package 2, 3 and 4 (2 and 4 are
+ // suspendable). We need to return package 3 as the unsuspended package here, and we ask
+ // PackageManager to suspend package 2, 3 and 4 here (who will return package 3 in the
+ // failed list, and package 2 is already suspended).
+ Set<String> result = suspendWithExemption(packages);
+ return result.toArray(String[]::new);
+ }
+
+ /**
+ * Suspend packages considering the exemption list.
+ *
+ * @return the list of packages that couldn't be suspended, either due to the exemption list,
+ * or due to failures from PackageManagerInternal itself.
+ */
+ private Set<String> suspendWithExemption(Set<String> packages) {
+ Set<String> packagesToSuspend = new ArraySet<>(packages);
+ // Any original packages that are also in the exempted list will not be suspended and hence
+ // will appear in the final result.
+ Set<String> result = new ArraySet<>(mExemptedPackages);
+ result.retainAll(packagesToSuspend);
+ // Remove exempted packages before calling PackageManager
+ packagesToSuspend.removeAll(mExemptedPackages);
+ String[] failedPackages = mPackageManager.setPackagesSuspendedByAdmin(
+ mUserId, packagesToSuspend.toArray(String[]::new), true);
+ if (failedPackages == null) {
+ Slogf.w(LOG_TAG, "PM failed to suspend packages (%s)", packages);
+ return packages;
+ } else {
+ result.addAll(Arrays.asList(failedPackages));
+ return result;
+ }
+ }
+
+ /**
+ * Unsuspend packages that are requested by a single admin
+ *
+ * @return a list of packages that the admin has requested to unsuspend but could not be
+ * unsuspended, due to other amdin's policy or PackageManager restriction.
+ *
+ */
+ public String[] unsuspend(Set<String> packages) {
+ // Unlike suspend(), when unsuspending, call PackageManager with the delta of resolved
+ // suspended packages list and not what the admin has requested. This is because some
+ // packages might still be subject to another admin's suspension request.
+ Set<String> packagesToUnsuspend = new ArraySet<>(mSuspendedPackageBefore);
+ packagesToUnsuspend.removeAll(mSuspendedPackageAfter);
+
+ // To calculate the result (which packages are not unsuspended), start with packages that
+ // are still subject to another admin's suspension policy. This is calculated by
+ // intersecting the packages argument with mSuspendedPackageAfter.
+ Set<String> result = new ArraySet<>(packages);
+ result.retainAll(mSuspendedPackageAfter);
+ // Remove mExemptedPackages since they can't be suspended to start with.
+ result.removeAll(mExemptedPackages);
+ // Finally make the unsuspend() request and add packages that PackageManager can't unsuspend
+ // to the result.
+ result.addAll(unsuspendWithExemption(packagesToUnsuspend));
+ return result.toArray(String[]::new);
+ }
+
+ /**
+ * Unsuspend packages considering the exemption list.
+ *
+ * @return the list of packages that couldn't be unsuspended, either due to the exemption list,
+ * or due to failures from PackageManagerInternal itself.
+ */
+ private Set<String> unsuspendWithExemption(Set<String> packages) {
+ // when unsuspending, no need to consider exemption list since by definition they can't
+ // be suspended to begin with.
+ String[] failedPackages = mPackageManager.setPackagesSuspendedByAdmin(
+ mUserId, packages.toArray(String[]::new), false);
+ if (failedPackages == null) {
+ Slogf.w(LOG_TAG, "PM failed to unsuspend packages (%s)", packages);
+ }
+ return new ArraySet<>(failedPackages);
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 8bec384..a0ea4e9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -240,7 +240,7 @@
POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE
| POLICY_FLAG_NON_COEXISTABLE_POLICY
| POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED,
- PolicyEnforcerCallbacks::setApplicationRestrictions,
+ PolicyEnforcerCallbacks::noOp,
new BundlePolicySerializer());
/**
@@ -263,7 +263,7 @@
new MostRecent<>(),
POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_NON_COEXISTABLE_POLICY,
// DevicePolicyManagerService handles the enforcement, this just takes care of storage
- (Long value, Context context, Integer userId, PolicyKey policyKey) -> true,
+ PolicyEnforcerCallbacks::noOp,
new LongPolicySerializer());
static PolicyDefinition<Integer> KEYGUARD_DISABLED_FEATURES = new PolicyDefinition<>(
@@ -271,7 +271,7 @@
new FlagUnion(),
POLICY_FLAG_LOCAL_ONLY_POLICY,
// Nothing is enforced for keyguard features, we just need to store it
- (Integer value, Context context, Integer userId, PolicyKey policyKey) -> true,
+ PolicyEnforcerCallbacks::noOp,
new IntegerPolicySerializer());
// This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
@@ -312,7 +312,7 @@
TRUE_MORE_RESTRICTIVE,
POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE,
// Nothing is enforced, we just need to store it
- (Boolean value, Context context, Integer userId, PolicyKey policyKey) -> true,
+ PolicyEnforcerCallbacks::noOp,
new BooleanPolicySerializer());
/**
@@ -332,7 +332,7 @@
new NoArgsPolicyKey(DevicePolicyIdentifiers.PERMITTED_INPUT_METHODS_POLICY),
new MostRecent<>(),
POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE,
- (Set<String> value, Context context, Integer userId, PolicyKey policyKey) -> true,
+ PolicyEnforcerCallbacks::noOp,
new PackageSetPolicySerializer());
@@ -366,6 +366,30 @@
PolicyEnforcerCallbacks::setContentProtectionPolicy,
new IntegerPolicySerializer());
+ static PolicyDefinition<Integer> PASSWORD_COMPLEXITY = new PolicyDefinition<>(
+ new NoArgsPolicyKey(DevicePolicyIdentifiers.PASSWORD_COMPLEXITY_POLICY),
+ new MostRestrictive<>(
+ List.of(
+ new IntegerPolicyValue(
+ DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH),
+ new IntegerPolicyValue(
+ DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM),
+ new IntegerPolicyValue(
+ DevicePolicyManager.PASSWORD_COMPLEXITY_LOW),
+ new IntegerPolicyValue(
+ DevicePolicyManager.PASSWORD_COMPLEXITY_NONE))),
+ POLICY_FLAG_LOCAL_ONLY_POLICY,
+ PolicyEnforcerCallbacks::noOp,
+ new IntegerPolicySerializer());
+
+ static PolicyDefinition<Set<String>> PACKAGES_SUSPENDED =
+ new PolicyDefinition<>(
+ new NoArgsPolicyKey(
+ DevicePolicyIdentifiers.PACKAGES_SUSPENDED_POLICY),
+ new PackageSetUnion(),
+ PolicyEnforcerCallbacks::noOp,
+ new PackageSetPolicySerializer());
+
private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>();
@@ -405,6 +429,13 @@
USB_DATA_SIGNALING);
POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.CONTENT_PROTECTION_POLICY,
CONTENT_PROTECTION);
+ // Intentionally not flagged since if the flag is flipped off on a device already
+ // having PASSWORD_COMPLEXITY policy in the on-device XML, it will cause the
+ // deserialization logic to break due to seeing an unknown tag.
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PASSWORD_COMPLEXITY_POLICY,
+ PASSWORD_COMPLEXITY);
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PACKAGES_SUSPENDED_POLICY,
+ PACKAGES_SUSPENDED);
// User Restriction Policies
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MODIFY_ACCOUNTS, /* flags= */ 0);
@@ -523,7 +554,7 @@
private final PolicyKey mPolicyKey;
private final ResolutionMechanism<V> mResolutionMechanism;
private final int mPolicyFlags;
- // A function that accepts policy to apple, context, userId, callback arguments, and returns
+ // A function that accepts policy to apply, context, userId, callback arguments, and returns
// true if the policy has been enforced successfully.
private final QuadFunction<V, Context, Integer, PolicyKey, Boolean> mPolicyEnforcerCallback;
private final PolicySerializer<V> mPolicySerializer;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 04d277e..e1cb37d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -73,6 +73,10 @@
private static final String LOG_TAG = "PolicyEnforcerCallbacks";
+ static <T> boolean noOp(T value, Context context, Integer userId, PolicyKey policyKey) {
+ return true;
+ }
+
static boolean setAutoTimezoneEnabled(@Nullable Boolean enabled, @NonNull Context context) {
if (!DevicePolicyManagerService.isUnicornFlagEnabled()) {
Slogf.w(LOG_TAG, "Trying to enforce setAutoTimezoneEnabled while flag is off.");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index c544ebc..245c438 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -200,7 +200,7 @@
pw.println(mPolicyDefinition.getPolicyKey());
pw.increaseIndent();
- pw.println("Per-admin Policy");
+ pw.println("Per-admin Policy:");
pw.increaseIndent();
if (mPoliciesSetByAdmins.size() == 0) {
pw.println("null");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ResolutionMechanism.java b/services/devicepolicy/java/com/android/server/devicepolicy/ResolutionMechanism.java
index c321aa1..ad7ac2b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ResolutionMechanism.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ResolutionMechanism.java
@@ -20,9 +20,24 @@
import android.app.admin.PolicyValue;
import java.util.LinkedHashMap;
+import java.util.List;
abstract class ResolutionMechanism<V> {
+ /**
+ * The most generic resolution logic where we know both the policy value and the admin who
+ * sets it.
+ */
@Nullable
abstract PolicyValue<V> resolve(LinkedHashMap<EnforcingAdmin, PolicyValue<V>> adminPolicies);
+
+ /**
+ * A special resolution logic that does not care about admins who set them. Only applicable to
+ * a subset of ResolutionMechanism.
+ */
+ @Nullable
+ PolicyValue<V> resolve(List<PolicyValue<V>> adminPolicies) {
+ throw new UnsupportedOperationException();
+ }
+
abstract android.app.admin.ResolutionMechanism<V> getParcelableResolutionMechanism();
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index cfe4e17..927df8b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -761,6 +761,9 @@
}
}
+ private static final long BINDER_CALLBACK_THROTTLE_MS = 10_100L;
+ private long mBinderCallbackLast = -1;
+
private void run() {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
try {
@@ -965,6 +968,14 @@
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/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 3628a57..d3efcb6 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -16,7 +16,6 @@
package com.android.server.display;
-import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
@@ -44,6 +43,7 @@
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.os.Handler;
import android.os.PowerManager;
+import android.os.SystemClock;
import android.os.test.TestLooper;
import android.util.SparseArray;
import android.view.Display;
@@ -54,6 +54,7 @@
import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.config.HysteresisLevels;
+import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.testutils.OffsettableClock;
import org.junit.After;
@@ -68,6 +69,8 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AutomaticBrightnessControllerTest {
+ private static final int ANDROID_SLEEP_TIME = 1000;
+ private static final int NANO_SECONDS_MULTIPLIER = 1000000;
private static final float BRIGHTNESS_MIN_FLOAT = 0.0f;
private static final float BRIGHTNESS_MAX_FLOAT = 1.0f;
private static final int LIGHT_SENSOR_RATE = 20;
@@ -100,6 +103,8 @@
@Mock BrightnessRangeController mBrightnessRangeController;
@Mock
BrightnessClamperController mBrightnessClamperController;
+ @Mock
+ DisplayManagerFlags mDisplayManagerFlags;
@Mock BrightnessThrottler mBrightnessThrottler;
@Before
@@ -148,8 +153,18 @@
}
@Override
- AutomaticBrightnessController.Clock createClock() {
- return mClock::now;
+ AutomaticBrightnessController.Clock createClock(boolean isEnabled) {
+ return new AutomaticBrightnessController.Clock() {
+ @Override
+ public long uptimeMillis() {
+ return mClock.now();
+ }
+
+ @Override
+ public long getSensorEventScaleTime() {
+ return mClock.now() + ANDROID_SLEEP_TIME;
+ }
+ };
}
}, // pass in test looper instead, pass in offsettable clock
@@ -166,7 +181,7 @@
mContext, mBrightnessRangeController, mBrightnessThrottler,
useHorizon ? AMBIENT_LIGHT_HORIZON_SHORT : 1,
useHorizon ? AMBIENT_LIGHT_HORIZON_LONG : 10000, userLux, userNits,
- mBrightnessClamperController
+ mBrightnessClamperController, mDisplayManagerFlags
);
when(mBrightnessRangeController.getCurrentBrightnessMax()).thenReturn(
@@ -350,7 +365,7 @@
when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
- mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE, /* sendUpdate= */ true);
when(mBrightnessMappingStrategy.shouldResetShortTermModel(
123f, 0.5f)).thenReturn(true);
@@ -360,7 +375,7 @@
mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000);
mTestLooper.dispatchAll();
- mController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT);
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT, /* sendUpdate= */ true);
mTestLooper.moveTimeForward(4000);
mTestLooper.dispatchAll();
@@ -394,14 +409,14 @@
when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.51f);
when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123.0f);
- mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE, /* sendUpdate= */ true);
// Time does not move forward, since clock is doesn't increment naturally.
mTestLooper.dispatchAll();
// Sensor reads 100000 lux,
listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 678910));
- mController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT);
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT, /* sendUpdate= */ true);
// Verify short term model is not reset.
verify(mBrightnessMappingStrategy, never()).clearUserDataPoints();
@@ -432,7 +447,7 @@
when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.5f);
when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123f);
- mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE, /* sendUpdate= */ true);
when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(
PowerManager.BRIGHTNESS_INVALID_FLOAT);
when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(
@@ -446,7 +461,7 @@
mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000);
mTestLooper.dispatchAll();
- mController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT);
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT, /* sendUpdate= */ true);
mTestLooper.moveTimeForward(4000);
mTestLooper.dispatchAll();
@@ -479,7 +494,7 @@
when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.5f);
when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123f);
- mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE, /* sendUpdate= */ true);
when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(
PowerManager.BRIGHTNESS_INVALID_FLOAT);
when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(
@@ -493,7 +508,7 @@
// Do not fast-forward time.
mTestLooper.dispatchAll();
- mController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT);
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT, /* sendUpdate= */ true);
// Do not fast-forward time
mTestLooper.dispatchAll();
@@ -523,7 +538,7 @@
// No user brightness interaction.
- mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE, /* sendUpdate= */ true);
when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(
PowerManager.BRIGHTNESS_INVALID_FLOAT);
when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(
@@ -534,7 +549,7 @@
// Do not fast-forward time.
mTestLooper.dispatchAll();
- mController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT);
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT, /* sendUpdate= */ true);
// Do not fast-forward time
mTestLooper.dispatchAll();
@@ -568,7 +583,7 @@
verify(mBrightnessMappingStrategy, times(3)).getBrightness(anyFloat(), any(), anyInt());
// Now let's do the same for idle mode
- mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE, /* sendUpdate= */ true);
// Called once when switching,
// setAmbientLux() is called twice and once in updateAutoBrightness(),
// nextAmbientLightBrighteningTransition() and nextAmbientLightDarkeningTransition() are
@@ -800,6 +815,43 @@
}
@Test
+ public void testAmbientLuxBuffers_prunedBeyondLongHorizonExceptLatestValue() throws Exception {
+ when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true);
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Choose values such that the ring buffer's capacity is extended and the buffer is pruned
+ int increment = 11;
+ int lux = 5000;
+ for (int i = 0; i < 1000; i++) {
+ lux += increment;
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
+ }
+ mClock.fastForward(AMBIENT_LIGHT_HORIZON_LONG + 10);
+ int newLux = 2000;
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, newLux,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
+
+ float[] sensorValues = mController.getLastSensorValues();
+ long[] sensorTimestamps = mController.getLastSensorTimestamps();
+ // Only the values within the horizon should be kept
+ assertEquals(2, sensorValues.length);
+ assertEquals(2, sensorTimestamps.length);
+
+ assertEquals(lux, sensorValues[0], EPSILON);
+ assertEquals(newLux, sensorValues[1], EPSILON);
+ assertEquals(mClock.now() + ANDROID_SLEEP_TIME - AMBIENT_LIGHT_HORIZON_LONG,
+ sensorTimestamps[0]);
+ assertEquals(mClock.now() + ANDROID_SLEEP_TIME,
+ sensorTimestamps[1]);
+ }
+
+ @Test
public void testGetSensorReadingsFullBuffer() throws Exception {
ArgumentCaptor<SensorEventListener> listenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
@@ -966,7 +1018,7 @@
BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true,
/* useHorizon= */ false);
- mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE, /* sendUpdate= */ true);
ArgumentCaptor<SensorEventListener> listenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
@@ -1003,7 +1055,7 @@
BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true,
/* useHorizon= */ false);
- mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE, /* sendUpdate= */ true);
ArgumentCaptor<SensorEventListener> listenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
@@ -1030,35 +1082,6 @@
}
@Test
- public void testBrightnessBasedOnLastUsedLux() throws Exception {
- ArgumentCaptor<SensorEventListener> listenerCaptor =
- ArgumentCaptor.forClass(SensorEventListener.class);
- verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
- eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
- SensorEventListener listener = listenerCaptor.getValue();
-
- // Set up system to return 0.3f as a brightness value
- float lux = 100.0f;
- // Brightness as float (from 0.0f to 1.0f)
- float normalizedBrightness = 0.3f;
- when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
- when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
- when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
- /* category= */ anyInt())).thenReturn(normalizedBrightness);
- when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
-
- // Send a new sensor value, disable the sensor and verify
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
- mController.configure(AUTO_BRIGHTNESS_DISABLED, /* configuration= */ null,
- /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0,
- /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT, Display.STATE_ON,
- /* shouldResetShortTermModel= */ true);
- assertEquals(normalizedBrightness,
- mController.getAutomaticScreenBrightnessBasedOnLastUsedLux(
- /* brightnessEvent= */ null), EPSILON);
- }
-
- @Test
public void testAutoBrightnessInDoze() throws Exception {
ArgumentCaptor<SensorEventListener> listenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
@@ -1089,9 +1112,6 @@
assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR,
mController.getAutomaticScreenBrightness(
/* brightnessEvent= */ null), EPSILON);
- assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR,
- mController.getAutomaticScreenBrightnessBasedOnLastUsedLux(
- /* brightnessEvent= */ null), EPSILON);
}
@Test
@@ -1113,7 +1133,7 @@
when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
// Switch mode to DOZE
- mController.switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_DOZE, /* sendUpdate= */ false);
// Set policy to DOZE
mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
@@ -1127,9 +1147,6 @@
// The brightness should not be scaled by the doze factor
assertEquals(normalizedBrightness,
mController.getAutomaticScreenBrightness(/* brightnessEvent= */ null), EPSILON);
- assertEquals(normalizedBrightness,
- mController.getAutomaticScreenBrightnessBasedOnLastUsedLux(
- /* brightnessEvent= */ null), EPSILON);
}
@Test
@@ -1162,8 +1179,63 @@
// The brightness should not be scaled by the doze factor
assertEquals(normalizedBrightness,
mController.getAutomaticScreenBrightness(/* brightnessEvent= */ null), EPSILON);
+ }
+
+ @Test
+ public void testSwitchMode_UpdateBrightnessImmediately() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Set up system to return 0.3f as a brightness value
+ float lux = 100.0f;
+ // Brightness as float (from 0.0f to 1.0f)
+ float normalizedBrightness = 0.3f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
+ when(mDozeBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
+ /* category= */ anyInt())).thenReturn(normalizedBrightness);
+ when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+
+ // Send a new sensor value
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
+
+ // Switch mode to DOZE
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_DOZE, /* sendUpdate= */ false);
+
assertEquals(normalizedBrightness,
- mController.getAutomaticScreenBrightnessBasedOnLastUsedLux(
- /* brightnessEvent= */ null), EPSILON);
+ mController.getAutomaticScreenBrightness(/* brightnessEvent= */ null), EPSILON);
+ }
+
+ @Test
+ public void testSwitchMode_UpdateBrightnessInBackground() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Set up system to return 0.3f as a brightness value
+ float lux = 100.0f;
+ // Brightness as float (from 0.0f to 1.0f)
+ float normalizedBrightness = 0.3f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
+ when(mDozeBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
+ /* category= */ anyInt())).thenReturn(normalizedBrightness);
+ when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+
+ // Send a new sensor value
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
+
+ // Switch mode to DOZE
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_DOZE, /* sendUpdate= */ true);
+ mClock.fastForward(SystemClock.uptimeMillis());
+ mTestLooper.dispatchAll();
+
+ assertEquals(normalizedBrightness,
+ mController.getAutomaticScreenBrightness(/* brightnessEvent= */ null), EPSILON);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 8844e6c..d0eb83a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -2513,9 +2513,8 @@
LogicalDisplay display =
logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
assertThat(display.isEnabledLocked()).isFalse();
- // TODO(b/332711269) make sure only one DISPLAY_GROUP_EVENT_ADDED sent.
assertThat(callback.receivedEvents()).containsExactly(DISPLAY_GROUP_EVENT_ADDED,
- DISPLAY_GROUP_EVENT_ADDED, EVENT_DISPLAY_CONNECTED).inOrder();
+ EVENT_DISPLAY_CONNECTED).inOrder();
}
@Test
@@ -3359,8 +3358,11 @@
}
displayDeviceInfo.address = new TestUtils.TestDisplayAddress();
displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
- displayManager.getDisplayDeviceRepository()
- .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
+
+ displayManager.getDisplayHandler().runWithScissors(() -> {
+ displayManager.getDisplayDeviceRepository()
+ .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
+ }, 0 /* now */);
return displayDevice;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index e5685c7..98f572d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1021,6 +1021,36 @@
}
@Test
+ public void testAutoBrightnessEnabled_DisplayIsInDoze_OffloadAllows() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+ when(mDisplayManagerFlagsMock.offloadControlsDozeAutoBrightness()).thenReturn(true);
+ when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true);
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.automaticBrightnessController).configure(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+ /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userChangedBrightness= */ false, /* adjustment= */ 0,
+ /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
+ Display.STATE_DOZE, /* shouldResetShortTermModel= */ false
+ );
+ verify(mHolder.hbmController)
+ .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
+ }
+
+ @Test
public void testAutoBrightnessDisabled_ManualBrightnessMode() {
Settings.System.putInt(mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS_MODE,
@@ -1067,7 +1097,7 @@
}
@Test
- public void testAutoBrightnessDisabled_DisplayIsInDoze() {
+ public void testAutoBrightnessDisabled_DisplayIsInDoze_ConfigDoesNotAllow() {
Settings.System.putInt(mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
@@ -1093,6 +1123,36 @@
}
@Test
+ public void testAutoBrightnessDisabled_DisplayIsInDoze_OffloadDoesNotAllow() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+ when(mDisplayManagerFlagsMock.offloadControlsDozeAutoBrightness()).thenReturn(true);
+ when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(false);
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.automaticBrightnessController).configure(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+ /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userChangedBrightness= */ false, /* adjustment= */ 0,
+ /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
+ Display.STATE_DOZE, /* shouldResetShortTermModel= */ false
+ );
+ verify(mHolder.hbmController).setAutoBrightnessEnabled(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
+ }
+
+ @Test
public void testAutoBrightnessDisabled_FollowerDisplay() {
Settings.System.putInt(mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS_MODE,
@@ -1191,7 +1251,8 @@
/* ambientLightHorizonLong= */ anyInt(),
eq(lux),
eq(nits),
- any(BrightnessClamperController.class)
+ any(BrightnessClamperController.class),
+ any(DisplayManagerFlags.class)
);
}
@@ -1668,7 +1729,8 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- verify(mHolder.automaticBrightnessController).switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+ verify(mHolder.automaticBrightnessController)
+ .switchMode(AUTO_BRIGHTNESS_MODE_DOZE, /* sendUpdate= */ false);
// Back to default mode
when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
@@ -1676,7 +1738,8 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- verify(mHolder.automaticBrightnessController).switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT);
+ verify(mHolder.automaticBrightnessController)
+ .switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT, /* sendUpdate= */ false);
}
@Test
@@ -1690,7 +1753,7 @@
advanceTime(1); // Run updatePowerState
verify(mHolder.automaticBrightnessController, never())
- .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+ .switchMode(eq(AUTO_BRIGHTNESS_MODE_DOZE), /* sendUpdate= */ anyBoolean());
}
@Test
@@ -1703,7 +1766,7 @@
advanceTime(1); // Run updatePowerState
verify(mHolder.automaticBrightnessController, never())
- .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+ .switchMode(eq(AUTO_BRIGHTNESS_MODE_DOZE), /* sendUpdate= */ anyBoolean());
}
@Test
@@ -1764,37 +1827,7 @@
}
@Test
- public void testInitialDozeBrightness_AutoBrightnessEnabled() {
- when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
- when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
- mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
- float brightness = 0.277f;
- when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(mHolder.automaticBrightnessController
- .getAutomaticScreenBrightnessBasedOnLastUsedLux(any(BrightnessEvent.class)))
- .thenReturn(brightness);
- when(mHolder.hbmController.getCurrentBrightnessMax())
- .thenReturn(PowerManager.BRIGHTNESS_MAX);
- when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
-
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- dpr.policy = DisplayPowerRequest.POLICY_DOZE;
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
-
- verify(mHolder.animator).animateTo(eq(brightness),
- /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
- /* ignoreAnimationLimits= */ anyBoolean());
- verify(mHolder.brightnessSetting).setBrightness(brightness);
- }
-
- @Test
- public void testInitialDozeBrightness_AutoBrightnessDisabled() {
+ public void testDozeManualBrightness() {
when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
Settings.System.putInt(mContext.getContentResolver(),
@@ -1827,22 +1860,17 @@
}
@Test
- public void testInitialDozeBrightness_AbcIsNull() {
- when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
+ public void testDozeManualBrightness_AbcIsNull() {
when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
- Settings.System.putInt(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true,
/* isAutoBrightnessAvailable= */ false);
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
float brightness = 0.277f;
when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
- when(mHolder.automaticBrightnessController
- .getAutomaticScreenBrightnessBasedOnLastUsedLux(any(BrightnessEvent.class)))
- .thenReturn(brightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
when(mHolder.hbmController.getCurrentBrightnessMax())
.thenReturn(PowerManager.BRIGHTNESS_MAX);
when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
@@ -1850,13 +1878,20 @@
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_DOZE;
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1); // Run updatePowerState
+ advanceTime(1); // Run updatePowerState, initialize
- // Automatic Brightness Controller is null so no initial doze brightness should be set and
- // we should not crash
- verify(mHolder.animator, never()).animateTo(eq(brightness),
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(brightness * DOZE_SCALE_FACTOR),
/* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
/* ignoreAnimationLimits= */ anyBoolean());
+ assertEquals(brightness * DOZE_SCALE_FACTOR, mHolder.dpc.getDozeBrightnessForOffload(),
+ /* delta= */ 0);
}
@Test
@@ -1864,6 +1899,8 @@
float brightness = 0.121f;
when(mPowerManagerMock.getBrightnessConstraint(
PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE)).thenReturn(brightness);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
@@ -1881,6 +1918,31 @@
eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
}
+ @Test
+ public void testDefaultDozeBrightness_ShouldNotBeUsedIfAutoBrightnessAllowedInDoze() {
+ float brightness = 0.121f;
+ when(mPowerManagerMock.getBrightnessConstraint(
+ PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE)).thenReturn(brightness);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ when(mHolder.hbmController.getCurrentBrightnessMax())
+ .thenReturn(PowerManager.BRIGHTNESS_MAX);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator, never()).animateTo(eq(brightness),
+ /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
+ /* ignoreAnimationLimits= */ anyBoolean());
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
@@ -2186,7 +2248,8 @@
BrightnessRangeController brightnessRangeController,
BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
int ambientLightHorizonLong, float userLux, float userNits,
- BrightnessClamperController brightnessClamperController) {
+ BrightnessClamperController brightnessClamperController,
+ DisplayManagerFlags displayManagerFlags) {
return mAutomaticBrightnessController;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
index ea08be4..82acaf8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -317,7 +317,7 @@
mDisplayEventCaptor.capture());
assertThat(mLogicalDisplayCaptor.getValue()).isEqualTo(mMockedLogicalDisplay);
assertThat(mDisplayEventCaptor.getValue()).isEqualTo(EVENT_DISPLAY_CONNECTED);
- verify(mMockedLogicalDisplayMapper).setDisplayEnabledLocked(eq(mMockedLogicalDisplay),
+ verify(mMockedLogicalDisplayMapper).setEnabledLocked(eq(mMockedLogicalDisplay),
eq(false));
clearInvocations(mMockedLogicalDisplayMapper);
clearInvocations(mMockedLogicalDisplay);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 7fd96c5..12050e1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -1246,6 +1246,11 @@
@Override
public void onBlockingScreenOn(Runnable unblocker) {}
+
+ @Override
+ public boolean allowAutoBrightnessInDoze() {
+ return true;
+ }
});
mDisplayOffloadSession = new DisplayOffloadSessionImpl(mDisplayOffloader,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/TestUtils.java b/services/tests/displayservicetests/src/com/android/server/display/TestUtils.java
index 8b45145..18dfcc1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/TestUtils.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/TestUtils.java
@@ -30,13 +30,21 @@
public final class TestUtils {
public static SensorEvent createSensorEvent(Sensor sensor, int value) throws Exception {
+ return createSensorEvent(sensor, value, SystemClock.elapsedRealtimeNanos());
+ }
+
+ /**
+ * Creates a light sensor event
+ */
+ public static SensorEvent createSensorEvent(Sensor sensor, int value, long timestamp)
+ throws Exception {
final Constructor<SensorEvent> constructor =
SensorEvent.class.getDeclaredConstructor(int.class);
constructor.setAccessible(true);
final SensorEvent event = constructor.newInstance(1);
event.sensor = sensor;
event.values[0] = value;
- event.timestamp = SystemClock.elapsedRealtimeNanos();
+ event.timestamp = timestamp;
return event;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java
index 09f5bb6..498bffd 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java
@@ -343,17 +343,11 @@
AutomaticBrightnessController.class);
when(automaticBrightnessController.getAutomaticScreenBrightness(any(BrightnessEvent.class)))
.thenReturn(automaticScreenBrightness);
- when(automaticBrightnessController.getAutomaticScreenBrightnessBasedOnLastUsedLux(
- any(BrightnessEvent.class)))
- .thenReturn(automaticScreenBrightness);
mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
automaticBrightnessController);
assertEquals(automaticScreenBrightness,
mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
new BrightnessEvent(DISPLAY_ID)), 0.0f);
- assertEquals(automaticScreenBrightness,
- mAutomaticBrightnessStrategy.getAutomaticScreenBrightnessBasedOnLastUsedLux(
- new BrightnessEvent(DISPLAY_ID)), 0.0f);
}
@Test
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 d19f479..afb5a5c 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
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
@@ -273,7 +274,8 @@
mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
- verify(mAutomaticBrightnessController, never()).switchMode(anyInt());
+ verify(mAutomaticBrightnessController, never())
+ .switchMode(anyInt(), /* sendUpdate= */ anyBoolean());
// Validate interaction when automaticBrightnessController is in non-idle mode, and display
// state is ON
@@ -282,7 +284,8 @@
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
verify(mAutomaticBrightnessController).switchMode(
- AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT);
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT,
+ /* sendUpdate= */ false);
// Validate interaction when automaticBrightnessController is in non-idle mode, and display
// state is DOZE
@@ -290,7 +293,8 @@
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
verify(mAutomaticBrightnessController).switchMode(
- AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE);
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE,
+ /* sendUpdate= */ false);
}
@Test
@@ -388,17 +392,11 @@
AutomaticBrightnessController.class);
when(automaticBrightnessController.getAutomaticScreenBrightness(any(BrightnessEvent.class)))
.thenReturn(automaticScreenBrightness);
- when(automaticBrightnessController.getAutomaticScreenBrightnessBasedOnLastUsedLux(
- any(BrightnessEvent.class)))
- .thenReturn(automaticScreenBrightness);
mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
automaticBrightnessController);
assertEquals(automaticScreenBrightness,
mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
new BrightnessEvent(DISPLAY_ID), false), 0.0f);
- assertEquals(automaticScreenBrightness,
- mAutomaticBrightnessStrategy.getAutomaticScreenBrightnessBasedOnLastUsedLux(
- new BrightnessEvent(DISPLAY_ID)), 0.0f);
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
index 3399565..396f4da 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
@@ -25,6 +25,7 @@
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_SYSTEM_APP_NO_AGENT;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSIONS_MATCH;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_V_TO_U_RESTORE_PKG_ELIGIBLE;
import static com.google.common.truth.Truth.assertThat;
@@ -42,17 +43,23 @@
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.content.pm.SigningInfo;
+import android.os.Build;
import android.os.Bundle;
import android.os.Process;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+import android.testing.TestableContext;
import androidx.test.InstrumentationRegistry;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.mockingservicestests.R;
import com.android.server.backup.FileMetadata;
+import com.android.server.backup.Flags;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.restore.PerformAdbRestoreTask;
import com.android.server.backup.restore.RestorePolicy;
@@ -61,6 +68,7 @@
import com.google.common.hash.Hashing;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -86,6 +94,8 @@
@Mock private BytesReadListener mBytesReadListenerMock;
@Mock private IBackupManagerMonitor mBackupManagerMonitorMock;
@Mock private PackageManagerInternal mMockPackageManagerInternal;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private final PackageManagerStub mPackageManagerStub = new PackageManagerStub();
private Context mContext;
@@ -95,7 +105,7 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mContext = InstrumentationRegistry.getContext();
+ mContext = new TestableContext(ApplicationProvider.getApplicationContext());
mUserId = UserHandle.USER_SYSTEM;
}
@@ -515,6 +525,107 @@
@Test
public void
+ chooseRestorePolicy_flagOnNotRestoreAnyVersionVToURestoreAndInAllowlist_returnsIgnore()
+ throws Exception {
+
+ mSetFlagsRule.enableFlags(
+ Flags.FLAG_ENABLE_V_TO_U_RESTORE_FOR_SYSTEM_COMPONENTS_IN_ALLOWLIST);
+
+ TarBackupReader tarBackupReader = createTarBackupReader();
+
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.V_TO_U_RESTORE_ALLOWLIST, "test");
+
+ Signature[] signatures = new Signature[]{FAKE_SIGNATURE_1};
+ FileMetadata info = new FileMetadata();
+ info.version = Build.VERSION_CODES.UPSIDE_DOWN_CAKE + 1;
+
+ PackageInfo packageInfo = createNonRestoreAnyVersionUPackage();
+ PackageManagerStub.sPackageInfo = packageInfo;
+
+ doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
+ packageInfo.packageName);
+ RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
+ false /* allowApks */, info, signatures, mMockPackageManagerInternal,
+ mUserId, mContext);
+
+ assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mBackupManagerMonitorMock).onEvent(bundleCaptor.capture());
+ assertThat(bundleCaptor.getValue().get(EXTRA_LOG_EVENT_ID)).isEqualTo(
+ LOG_EVENT_ID_V_TO_U_RESTORE_PKG_ELIGIBLE);
+ }
+
+
+ @Test
+ public void
+ chooseRestorePolicy_flagOffNotRestoreAnyVersionVToURestoreAndInAllowlist_returnsAccept()
+ throws Exception {
+
+ mSetFlagsRule.disableFlags(
+ Flags.FLAG_ENABLE_V_TO_U_RESTORE_FOR_SYSTEM_COMPONENTS_IN_ALLOWLIST);
+
+ TarBackupReader tarBackupReader = createTarBackupReader();
+
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.V_TO_U_RESTORE_ALLOWLIST, "test");
+
+ Signature[] signatures = new Signature[]{FAKE_SIGNATURE_1};
+ FileMetadata info = new FileMetadata();
+ info.version = Build.VERSION_CODES.UPSIDE_DOWN_CAKE + 1;
+
+ PackageInfo packageInfo = createNonRestoreAnyVersionUPackage();
+ PackageManagerStub.sPackageInfo = packageInfo;
+
+ doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
+ packageInfo.packageName);
+ RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
+ false /* allowApks */, info, signatures, mMockPackageManagerInternal,
+ mUserId, mContext);
+
+ assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mBackupManagerMonitorMock).onEvent(bundleCaptor.capture());
+ assertThat(bundleCaptor.getValue().get(EXTRA_LOG_EVENT_ID)).isEqualTo(
+ LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER);
+
+ }
+
+ @Test
+ public void
+ chooseRestorePolicy_flagOnNotRestoreAnyVersionVToURestoreAndNotInAllowlist_returnsIgnore()
+ throws Exception {
+
+ mSetFlagsRule.enableFlags(
+ Flags.FLAG_ENABLE_V_TO_U_RESTORE_FOR_SYSTEM_COMPONENTS_IN_ALLOWLIST);
+
+ TarBackupReader tarBackupReader = createTarBackupReader();
+
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.V_TO_U_RESTORE_ALLOWLIST, "pkg");
+
+ Signature[] signatures = new Signature[]{FAKE_SIGNATURE_1};
+ FileMetadata info = new FileMetadata();
+ info.version = Build.VERSION_CODES.UPSIDE_DOWN_CAKE + 1;
+
+ PackageInfo packageInfo = createNonRestoreAnyVersionUPackage();
+ PackageManagerStub.sPackageInfo = packageInfo;
+
+ doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
+ packageInfo.packageName);
+ RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
+ false /* allowApks */, info, signatures, mMockPackageManagerInternal,
+ mUserId, mContext);
+
+ assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mBackupManagerMonitorMock).onEvent(bundleCaptor.capture());
+ assertThat(bundleCaptor.getValue().get(EXTRA_LOG_EVENT_ID)).isEqualTo(
+ LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER);
+ }
+
+ @Test
+ public void
chooseRestorePolicy_notRestoreAnyVersionAndVersionMismatchButAllowApksAndHasApk_returnsAcceptIfApk()
throws Exception {
InputStream inputStream = mContext.getResources().openRawResource(
@@ -523,6 +634,10 @@
inputStream, null);
TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
mBytesReadListenerMock, mBackupManagerMonitorMock);
+
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.V_TO_U_RESTORE_ALLOWLIST, "pkg");
+
Signature[] signatures = new Signature[]{FAKE_SIGNATURE_1};
FileMetadata info = new FileMetadata();
info.version = 2;
@@ -564,6 +679,10 @@
inputStream, null);
TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
mBytesReadListenerMock, mBackupManagerMonitorMock);
+
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.V_TO_U_RESTORE_ALLOWLIST, "pkg");
+
Signature[] signatures = new Signature[]{FAKE_SIGNATURE_1};
FileMetadata info = new FileMetadata();
info.version = 2;
@@ -596,5 +715,33 @@
assertThat(bundleCaptor.getValue().get(EXTRA_LOG_EVENT_ID)).isEqualTo(
LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER);
}
+
+ private TarBackupReader createTarBackupReader() throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_no_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, null);
+ TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
+ mBytesReadListenerMock, mBackupManagerMonitorMock);
+ return tarBackupReader;
+ }
+
+ private PackageInfo createNonRestoreAnyVersionUPackage(){
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = "test";
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
+ packageInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
+ packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
+ packageInfo.applicationInfo.backupAgentName = null;
+ packageInfo.signingInfo = new SigningInfo(
+ new SigningDetails(
+ new Signature[]{FAKE_SIGNATURE_1},
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null));
+ packageInfo.versionCode = Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+ return packageInfo;
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/GnssNativeTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/GnssNativeTest.java
new file mode 100644
index 0000000..a14bcaf
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/GnssNativeTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.location.gnss.hal;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.location.gnss.GnssConfiguration;
+import com.android.server.location.gnss.GnssPowerStats;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssNativeTest {
+
+ private @Mock Context mContext;
+ private @Mock GnssConfiguration mMockConfiguration;
+ private FakeGnssHal mFakeGnssHal;
+ private GnssNative mGnssNative;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mFakeGnssHal = new FakeGnssHal();
+ GnssNative.setGnssHalForTest(mFakeGnssHal);
+ Injector injector = new TestInjector(mContext);
+ mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+ mGnssNative.register();
+ }
+
+ @Test
+ public void testRequestPowerStats_onNull_executesCallbackWithNull() {
+ mFakeGnssHal.setPowerStats(null);
+ Executor executor = spy(Runnable::run);
+ GnssNative.PowerStatsCallback callback = spy(stats -> {});
+
+ mGnssNative.requestPowerStats(executor, callback);
+
+ verify(executor).execute(any());
+ verify(callback).onReportPowerStats(null);
+ }
+
+ @Test
+ public void testRequestPowerStats_onPowerStats_executesCallbackWithStats() {
+ GnssPowerStats powerStats = new GnssPowerStats(1, 2, 3, 4, 5, 6, 7, 8, new double[]{9, 10});
+ mFakeGnssHal.setPowerStats(powerStats);
+ Executor executor = spy(Runnable::run);
+ GnssNative.PowerStatsCallback callback = spy(stats -> {});
+
+ mGnssNative.requestPowerStats(executor, callback);
+
+ verify(executor).execute(any());
+ verify(callback).onReportPowerStats(powerStats);
+ }
+
+ @Test
+ public void testRequestPowerStatsBlocking_onNull_returnsNull() {
+ mFakeGnssHal.setPowerStats(null);
+
+ assertThat(mGnssNative.requestPowerStatsBlocking()).isNull();
+ }
+
+ @Test
+ public void testRequestPowerStatsBlocking_onPowerStats_returnsStats() {
+ GnssPowerStats powerStats = new GnssPowerStats(1, 2, 3, 4, 5, 6, 7, 8, new double[]{9, 10});
+ mFakeGnssHal.setPowerStats(powerStats);
+
+ assertThat(mGnssNative.requestPowerStatsBlocking()).isEqualTo(powerStats);
+ }
+
+ @Test
+ public void testGetLastKnownPowerStats_onNull_preservesLastKnownPowerStats() {
+ GnssPowerStats powerStats = new GnssPowerStats(1, 2, 3, 4, 5, 6, 7, 8, new double[]{9, 10});
+
+ mGnssNative.reportGnssPowerStats(powerStats);
+ assertThat(mGnssNative.getLastKnownPowerStats()).isEqualTo(powerStats);
+
+ mGnssNative.reportGnssPowerStats(null);
+ assertThat(mGnssNative.getLastKnownPowerStats()).isEqualTo(powerStats);
+ }
+
+ @Test
+ public void testGetLastKnownPowerStats_onPowerStats_updatesLastKnownPowerStats() {
+ GnssPowerStats powerStats1 = new GnssPowerStats(1, 2, 3, 4, 5, 6, 7, 8, new double[]{9, 0});
+ GnssPowerStats powerStats2 = new GnssPowerStats(2, 3, 4, 5, 6, 7, 8, 9, new double[]{0, 9});
+
+ mGnssNative.reportGnssPowerStats(powerStats1);
+ assertThat(mGnssNative.getLastKnownPowerStats()).isEqualTo(powerStats1);
+
+ mGnssNative.reportGnssPowerStats(powerStats2);
+ assertThat(mGnssNative.getLastKnownPowerStats()).isEqualTo(powerStats2);
+ }
+
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BinaryStatePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BinaryStatePowerStatsProcessorTest.java
new file mode 100644
index 0000000..be1c121
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BinaryStatePowerStatsProcessorTest.java
@@ -0,0 +1,283 @@
+/*
+ * 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.power.stats;
+
+import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
+
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.PersistableBundle;
+import android.os.Process;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.os.MonotonicClock;
+import com.android.internal.os.PowerStats;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+public class BinaryStatePowerStatsProcessorTest {
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
+
+ private static final double PRECISION = 0.00001;
+ private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+ private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
+ private static final int POWER_COMPONENT = BatteryConsumer.POWER_COMPONENT_AUDIO;
+ private static final int TEST_STATE_FLAG = 0x1;
+
+ private final MockClock mClock = new MockClock();
+ private final MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
+ private final PowerStatsUidResolver mUidResolver = new PowerStatsUidResolver();
+
+ private static class TestBinaryStatePowerStatsProcessor extends BinaryStatePowerStatsProcessor {
+ TestBinaryStatePowerStatsProcessor(int powerComponentId,
+ double averagePowerMilliAmp, PowerStatsUidResolver uidResolver) {
+ super(powerComponentId, uidResolver, averagePowerMilliAmp);
+ }
+
+ @Override
+ protected int getBinaryState(BatteryStats.HistoryItem item) {
+ return (item.states & TEST_STATE_FLAG) != 0 ? STATE_ON : STATE_OFF;
+ }
+ }
+
+ @Test
+ public void powerProfileModel() {
+ TestBinaryStatePowerStatsProcessor processor = new TestBinaryStatePowerStatsProcessor(
+ POWER_COMPONENT, /* averagePowerMilliAmp */ 100, mUidResolver);
+
+ BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
+
+ PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(processor);
+
+ processor.noteStateChange(stats, buildHistoryItem(0, true, APP_UID1));
+
+ // Turn the screen off after 2.5 seconds
+ stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000);
+
+ processor.noteStateChange(stats, buildHistoryItem(6000, false, APP_UID1));
+
+ processor.noteStateChange(stats, buildHistoryItem(7000, true, APP_UID2));
+
+ processor.finish(stats, 11000);
+
+ // Total usage duration is 10000
+ // Total estimated power = 10000 * 100 = 1000000 mA-ms = 0.277777 mAh
+ // Screen-on - 25%
+ // Screen-off - 75%
+ double expectedPower = 0.277778;
+ long[] deviceStats = new long[stats.getPowerStatsDescriptor().statsArrayLength];
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(expectedPower * 0.25);
+
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(expectedPower * 0.75);
+
+ // UID1 =
+ // 6000 * 100 = 600000 mA-ms = 0.166666 mAh
+ // split between three different states
+ double expectedPower1 = 0.166666;
+ long[] uidStats = new long[stats.getPowerStatsDescriptor().uidStatsArrayLength];
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000);
+
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000);
+
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 1000 / 6000);
+
+ // UID2 =
+ // 4000 * 100 = 400000 mA-ms = 0.111111 mAh
+ // all in the same state
+ double expectedPower2 = 0.111111;
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower2);
+
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0);
+ }
+
+ @Test
+ public void energyConsumerModel() {
+ TestBinaryStatePowerStatsProcessor processor = new TestBinaryStatePowerStatsProcessor(
+ POWER_COMPONENT, /* averagePowerMilliAmp */ 100, mUidResolver);
+
+ BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
+ PersistableBundle extras = new PersistableBundle();
+ statsLayout.toExtras(extras);
+ PowerStats.Descriptor descriptor = new PowerStats.Descriptor(POWER_COMPONENT,
+ statsLayout.getDeviceStatsArrayLength(), null, 0,
+ statsLayout.getUidStatsArrayLength(), extras);
+ PowerStats powerStats = new PowerStats(descriptor);
+ powerStats.stats = new long[descriptor.statsArrayLength];
+
+ PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(processor);
+
+ // Establish a baseline
+ processor.addPowerStats(stats, powerStats, mMonotonicClock.monotonicTime());
+
+ processor.noteStateChange(stats, buildHistoryItem(0, true, APP_UID1));
+
+ // Turn the screen off after 2.5 seconds
+ stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000);
+
+ processor.noteStateChange(stats, buildHistoryItem(6000, false, APP_UID1));
+
+ statsLayout.setConsumedEnergy(powerStats.stats, 0, 2_160_000);
+ processor.addPowerStats(stats, powerStats, mMonotonicClock.monotonicTime());
+
+ processor.noteStateChange(stats, buildHistoryItem(7000, true, APP_UID2));
+
+ mClock.realtime = 11000;
+ statsLayout.setConsumedEnergy(powerStats.stats, 0, 1_440_000);
+ processor.addPowerStats(stats, powerStats, mMonotonicClock.monotonicTime());
+
+ processor.finish(stats, 11000);
+
+ // Total estimated power = 3,600,000 uC = 1.0 mAh
+ // of which 3,000,000 is distributed:
+ // Screen-on - 2500/6000 * 2160000 = 900000 uC = 0.25 mAh
+ // Screen-off - 3500/6000 * 2160000 = 1260000 uC = 0.35 mAh
+ // and 600,000 was fully with screen off:
+ // Screen-off - 1440000 uC = 0.4 mAh
+ long[] deviceStats = new long[descriptor.statsArrayLength];
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(0.25);
+
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(0.35 + 0.4);
+
+ // UID1 =
+ // 2,160,000 uC = 0.6 mAh
+ // split between three different states
+ // fg screen-on: 2500/6000
+ // bg screen-off: 2500/6000
+ // fgs screen-off: 1000/6000
+ double expectedPower1 = 0.6;
+ long[] uidStats = new long[descriptor.uidStatsArrayLength];
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000);
+
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000);
+
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 1000 / 6000);
+
+ // UID2 =
+ // 1440000 mA-ms = 0.4 mAh
+ // all in the same state
+ double expectedPower2 = 0.4;
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower2);
+
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0);
+ }
+
+
+ @NonNull
+ private BatteryStats.HistoryItem buildHistoryItem(int elapsedRealtime, boolean stateOn,
+ int uid) {
+ mClock.realtime = elapsedRealtime;
+ BatteryStats.HistoryItem historyItem = new BatteryStats.HistoryItem();
+ historyItem.time = mMonotonicClock.monotonicTime();
+ historyItem.states = stateOn ? TEST_STATE_FLAG : 0;
+ if (stateOn) {
+ historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+ | BatteryStats.HistoryItem.EVENT_FLAG_START;
+ } else {
+ historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+ | BatteryStats.HistoryItem.EVENT_FLAG_FINISH;
+ }
+ historyItem.eventTag = historyItem.localEventTag;
+ historyItem.eventTag.uid = uid;
+ historyItem.eventTag.string = "test";
+ return historyItem;
+ }
+
+ private int[] states(int... states) {
+ return states;
+ }
+
+ private static PowerComponentAggregatedPowerStats createAggregatedPowerStats(
+ BinaryStatePowerStatsProcessor processor) {
+ AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+ config.trackPowerComponent(POWER_COMPONENT)
+ .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+ .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
+ .setProcessor(processor);
+
+ AggregatedPowerStats aggregatedPowerStats = new AggregatedPowerStats(config);
+ PowerComponentAggregatedPowerStats powerComponentStats =
+ aggregatedPowerStats.getPowerComponentStats(POWER_COMPONENT);
+ processor.start(powerComponentStats, 0);
+
+ powerComponentStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
+ powerComponentStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
+ powerComponentStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
+ powerComponentStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
+
+ return powerComponentStats;
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerStatsProcessorTest.java
index 752bc27..c88f0a9 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerStatsProcessorTest.java
@@ -200,7 +200,7 @@
aggregatedStats.addPowerStats(collector.collectStats(), 10_000);
- processor.finish(aggregatedStats);
+ processor.finish(aggregatedStats, 10_000);
BluetoothPowerStatsLayout statsLayout =
new BluetoothPowerStatsLayout(aggregatedStats.getPowerStatsDescriptor());
@@ -301,7 +301,7 @@
aggregatedStats.addPowerStats(collector.collectStats(), 10_000);
- processor.finish(aggregatedStats);
+ processor.finish(aggregatedStats, 10_000);
BluetoothPowerStatsLayout statsLayout =
new BluetoothPowerStatsLayout(aggregatedStats.getPowerStatsDescriptor());
@@ -408,7 +408,7 @@
aggregatedStats.addPowerStats(collector.collectStats(), 10_000);
- processor.finish(aggregatedStats);
+ processor.finish(aggregatedStats, 10_000);
BluetoothPowerStatsLayout statsLayout =
new BluetoothPowerStatsLayout(aggregatedStats.getPowerStatsDescriptor());
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsProcessorTest.java
index 6b5da81..b6b759e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsProcessorTest.java
@@ -128,7 +128,7 @@
states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED),
values(1500, 2000, 1000), 1.252578);
- mProcessor.finish(mStats);
+ mProcessor.finish(mStats, 10_000);
mStats.verifyPowerEstimates();
}
@@ -173,7 +173,7 @@
states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED),
values(1500, 2000, 1000), 0.80773);
- mProcessor.finish(mStats);
+ mProcessor.finish(mStats, 10_000);
mStats.verifyPowerEstimates();
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
index 29ef3b6..137c2a6 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
@@ -228,7 +228,7 @@
aggregatedStats.addPowerStats(powerStats, 10_000);
- processor.finish(aggregatedStats);
+ processor.finish(aggregatedStats, 10_000);
MobileRadioPowerStatsLayout statsLayout =
new MobileRadioPowerStatsLayout(
@@ -475,7 +475,7 @@
aggregatedStats.addPowerStats(powerStats, 10_000);
- processor.finish(aggregatedStats);
+ processor.finish(aggregatedStats, 10_000);
return aggregatedStats;
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
index 69d655b..548d54c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
@@ -203,11 +203,11 @@
aggregatedPowerStats.addPowerStats(collector.collectStats(), 10_000);
- mobileStatsProcessor.finish(mobileRadioStats);
+ mobileStatsProcessor.finish(mobileRadioStats, 10_000);
PowerComponentAggregatedPowerStats stats =
aggregatedPowerStats.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_PHONE);
- phoneStatsProcessor.finish(stats);
+ phoneStatsProcessor.finish(stats, 10_000);
PowerStatsLayout statsLayout =
new PowerStatsLayout(stats.getPowerStatsDescriptor());
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
index 3ceaf35..ff56691 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
@@ -234,7 +234,7 @@
aggregatedStats.addPowerStats(collector.collectStats(), 10_000);
- processor.finish(aggregatedStats);
+ processor.finish(aggregatedStats, 10_000);
WifiPowerStatsLayout statsLayout =
new WifiPowerStatsLayout(aggregatedStats.getPowerStatsDescriptor());
@@ -355,7 +355,7 @@
aggregatedStats.addPowerStats(collector.collectStats(), 10_000);
- processor.finish(aggregatedStats);
+ processor.finish(aggregatedStats, 10_000);
WifiPowerStatsLayout statsLayout =
new WifiPowerStatsLayout(aggregatedStats.getPowerStatsDescriptor());
@@ -454,7 +454,7 @@
aggregatedStats.addPowerStats(collector.collectStats(), 10_000);
- processor.finish(aggregatedStats);
+ processor.finish(aggregatedStats, 10_000);
WifiPowerStatsLayout statsLayout =
new WifiPowerStatsLayout(aggregatedStats.getPowerStatsDescriptor());
diff --git a/services/tests/servicestests/assets/AppOpsPersistenceTest/recent_accesses.xml b/services/tests/servicestests/assets/AppOpsPersistenceTest/recent_accesses.xml
new file mode 100644
index 0000000..5aceea3
--- /dev/null
+++ b/services/tests/servicestests/assets/AppOpsPersistenceTest/recent_accesses.xml
@@ -0,0 +1,11 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<app-ops v="1">
+ <pkg n="com.android.servicestests.apps.testapp">
+ <uid n="10001">
+ <op n="26">
+ <st id="attribution.tag.test.1" n="429496729601" t="1710799464518" d="2963" />
+ <st n="1073741824008" dv="companion:1" t="1712610342977" d="7596" pp="com.android.servicestests.apps.proxy" pc="com.android.servicestests.apps.proxy.attrtag" pu="10002" pdv="companion:2" />
+ </op>
+ </uid>
+ </pkg>
+</app-ops>
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java
new file mode 100644
index 0000000..c4b3c149
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.appop;
+
+import static android.app.AppOpsManager.OP_FLAGS_ALL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import android.app.AppOpsManager;
+import android.companion.virtual.VirtualDeviceManager;
+import android.content.Context;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.SparseArray;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.LocalServices;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+@RunWith(AndroidJUnit4.class)
+public class AppOpsRecentAccessPersistenceTest {
+ private static final String TAG = AppOpsRecentAccessPersistenceTest.class.getSimpleName();
+ private static final String TEST_XML = "AppOpsPersistenceTest/recent_accesses.xml";
+
+ private final Context mContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+ private File mMockDataDirectory;
+ private File mRecentAccessFile;
+ private AppOpsService mAppOpsService;
+
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+ @Mock private AppOpsServiceTestingShim mAppOpCheckingService;
+
+ @Before
+ public void setUp() {
+ when(mAppOpCheckingService.addAppOpsModeChangedListener(any())).thenReturn(true);
+ LocalServices.addService(AppOpsCheckingServiceInterface.class, mAppOpCheckingService);
+
+ mMockDataDirectory = mContext.getDir("mock_data", Context.MODE_PRIVATE);
+ mRecentAccessFile = new File(mMockDataDirectory, "test_accesses.xml");
+
+ HandlerThread handlerThread = new HandlerThread(TAG);
+ handlerThread.start();
+ Handler handler = new Handler(handlerThread.getLooper());
+ mAppOpsService = new AppOpsService(mRecentAccessFile, mRecentAccessFile, handler, mContext);
+ }
+
+ @After
+ public void cleanUp() {
+ FileUtils.deleteContents(mMockDataDirectory);
+ }
+
+ @Test
+ public void readAndWriteRecentAccesses() throws Exception {
+ copyRecentAccessFromAsset(mContext, TEST_XML, mRecentAccessFile);
+ SparseArray<AppOpsService.UidState> uidStates = new SparseArray<>();
+
+ AtomicFile recentAccessFile = new AtomicFile(mRecentAccessFile);
+ AppOpsRecentAccessPersistence persistence =
+ new AppOpsRecentAccessPersistence(recentAccessFile, mAppOpsService);
+
+ persistence.readRecentAccesses(uidStates);
+ validateUidStates(uidStates);
+
+ // Now we clear the xml file and write uidStates to it, then read again to verify data
+ // written to the xml is correct.
+ recentAccessFile.delete();
+ persistence.writeRecentAccesses(uidStates);
+
+ SparseArray<AppOpsService.UidState> newUidStates = new SparseArray<>();
+ persistence.readRecentAccesses(newUidStates);
+ validateUidStates(newUidStates);
+ }
+
+ // We compare data loaded into uidStates with original data in recent_accesses.xml
+ private void validateUidStates(SparseArray<AppOpsService.UidState> uidStates) {
+ assertThat(uidStates.size()).isEqualTo(1);
+
+ AppOpsService.UidState uidState = uidStates.get(10001);
+ assertThat(uidState.uid).isEqualTo(10001);
+
+ ArrayMap<String, AppOpsService.Ops> packageOps = uidState.pkgOps;
+ assertThat(packageOps.size()).isEqualTo(1);
+
+ AppOpsService.Ops ops = packageOps.get("com.android.servicestests.apps.testapp");
+ assertThat(ops.size()).isEqualTo(1);
+
+ AppOpsService.Op op = ops.get(26);
+ assertThat(op.mDeviceAttributedOps.size()).isEqualTo(2);
+
+ // Test AppOp access for the default device
+ AttributedOp attributedOp =
+ op.mDeviceAttributedOps
+ .get(VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)
+ .get("attribution.tag.test.1");
+ assertThat(attributedOp.persistentDeviceId)
+ .isEqualTo(VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+ assertThat(attributedOp.tag).isEqualTo("attribution.tag.test.1");
+
+ AppOpsManager.AttributedOpEntry attributedOpEntry =
+ attributedOp.createAttributedOpEntryLocked();
+
+ assertThat(attributedOpEntry.getLastAccessTime(OP_FLAGS_ALL)).isEqualTo(1710799464518L);
+ assertThat(attributedOpEntry.getLastDuration(OP_FLAGS_ALL)).isEqualTo(2963);
+
+ // Test AppOp access for an external device
+ AttributedOp attributedOpForDevice = op.mDeviceAttributedOps.get("companion:1").get(null);
+ assertThat(attributedOpForDevice.persistentDeviceId).isEqualTo("companion:1");
+
+ AppOpsManager.AttributedOpEntry attributedOpEntryForDevice =
+ attributedOpForDevice.createAttributedOpEntryLocked();
+ assertThat(attributedOpEntryForDevice.getLastAccessTime(OP_FLAGS_ALL))
+ .isEqualTo(1712610342977L);
+ assertThat(attributedOpEntryForDevice.getLastDuration(OP_FLAGS_ALL)).isEqualTo(7596);
+
+ AppOpsManager.OpEventProxyInfo proxyInfo =
+ attributedOpEntryForDevice.getLastProxyInfo(OP_FLAGS_ALL);
+ assertThat(proxyInfo.getUid()).isEqualTo(10002);
+ assertThat(proxyInfo.getPackageName()).isEqualTo("com.android.servicestests.apps.proxy");
+ assertThat(proxyInfo.getAttributionTag())
+ .isEqualTo("com.android.servicestests.apps.proxy.attrtag");
+ assertThat(proxyInfo.getDeviceId()).isEqualTo("companion:2");
+ }
+
+ private static void copyRecentAccessFromAsset(Context context, String xmlAsset, File outFile)
+ throws IOException {
+ writeToFile(outFile, readAsset(context, xmlAsset));
+ }
+
+ private static String readAsset(Context context, String assetPath) throws IOException {
+ final StringBuilder sb = new StringBuilder();
+ try (BufferedReader br =
+ new BufferedReader(
+ new InputStreamReader(
+ context.getResources().getAssets().open(assetPath)))) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ sb.append(line);
+ sb.append(System.lineSeparator());
+ }
+ }
+ return sb.toString();
+ }
+
+ private static void writeToFile(File path, String content) throws IOException {
+ path.getParentFile().mkdirs();
+
+ try (FileWriter writer = new FileWriter(path)) {
+ writer.write(content);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 2470403..e72d9e7 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -5590,6 +5590,12 @@
mContext.binder.callingUid = managedProfileAdminUid;
addManagedProfile(admin1, managedProfileAdminUid, admin1, VERSION_CODES.R);
+ // Profile has a unified challenge
+ doReturn(false).when(getServices().lockPatternUtils)
+ .isSeparateProfileChallengeEnabled(managedProfileUserId);
+ doReturn(true).when(getServices().lockPatternUtils)
+ .isProfileWithUnifiedChallenge(managedProfileUserId);
+
dpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
parentDpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
@@ -5610,6 +5616,12 @@
mContext.binder.callingUid = managedProfileAdminUid;
addManagedProfile(admin1, managedProfileAdminUid, admin1, VERSION_CODES.R);
+ // Profile has a unified challenge
+ doReturn(false).when(getServices().lockPatternUtils)
+ .isSeparateProfileChallengeEnabled(managedProfileUserId);
+ doReturn(true).when(getServices().lockPatternUtils)
+ .isProfileWithUnifiedChallenge(managedProfileUserId);
+
dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
dpm.setPasswordMinimumLength(admin1, 8);
dpm.setPasswordMinimumLetters(admin1, 1);
@@ -5870,6 +5882,8 @@
.thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
doReturn(separateChallenge).when(getServices().lockPatternUtils)
.isSeparateProfileChallengeEnabled(userId);
+ doReturn(!separateChallenge).when(getServices().lockPatternUtils)
+ .isProfileWithUnifiedChallenge(userId);
when(getServices().userManager.getCredentialOwnerProfile(userId))
.thenReturn(separateChallenge ? userId : UserHandle.USER_SYSTEM);
when(getServices().lockSettingsInternal.getUserPasswordMetrics(userId))
@@ -7631,6 +7645,7 @@
addManagedProfile(admin1, managedProfileAdminUid, admin1);
mContext.binder.callingUid = managedProfileAdminUid;
+ when(getServices().userManager.isManagedProfile()).thenReturn(true);
final Set<Integer> allowedModes = Set.of(PASSWORD_COMPLEXITY_NONE, PASSWORD_COMPLEXITY_LOW,
PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_COMPLEXITY_HIGH);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
index c1ae852..6d86301 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
@@ -86,6 +86,8 @@
mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy));
mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
mNativeWrapper = new FakeNativeWrapper();
+ mPhysicalAddress = 0x2000;
+ mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(hdmiCecController);
@@ -94,8 +96,6 @@
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
- mPhysicalAddress = 0x2000;
- mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
mTestLooper.dispatchAll();
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
index e669e7c..2296911 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
@@ -104,6 +104,8 @@
mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy));
mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
mNativeWrapper = new FakeNativeWrapper();
+ mPhysicalAddress = 0x2000;
+ mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(hdmiCecController);
@@ -112,8 +114,6 @@
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
- mPhysicalAddress = 0x2000;
- mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
mTestLooper.dispatchAll();
mPlaybackDevice = mHdmiControlService.playback();
mDevicePowerStatusAction = DevicePowerStatusAction.create(mPlaybackDevice, ADDR_TV,
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
index 29d20a6..47cfa42 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
@@ -137,6 +137,7 @@
mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
mNativeWrapper = new FakeNativeWrapper();
+ mNativeWrapper.setPhysicalAddress(0x0000);
mHdmiCecController = HdmiCecController.createWithNativeWrapper(
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
@@ -150,7 +151,7 @@
mHdmiControlService.initService();
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
- mNativeWrapper.setPhysicalAddress(0x0000);
+
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
index d32b75b..eb4a628 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
@@ -146,6 +146,7 @@
mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
mNativeWrapper = new FakeNativeWrapper();
+ mNativeWrapper.setPhysicalAddress(0x0000);
mHdmiCecController = HdmiCecController.createWithNativeWrapper(
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
@@ -168,7 +169,6 @@
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
- mNativeWrapper.setPhysicalAddress(0x0000);
mTestLooper.dispatchAll();
mNativeWrapper.clearResultMessages();
mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_PLAYBACK_1);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
index cb19029..bfe435c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
@@ -62,6 +62,7 @@
private HdmiCecController.HdmiCecCallback mCallback = null;
private int mCecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0;
private boolean mIsCecControlEnabled = true;
+ private boolean mGetPhysicalAddressCalled = false;
@Override
public String nativeInit() {
@@ -96,6 +97,7 @@
@Override
public int nativeGetPhysicalAddress() {
+ mGetPhysicalAddressCalled = true;
return mMyPhysicalAddress;
}
@@ -161,6 +163,10 @@
return mIsCecControlEnabled;
}
+ public boolean getPhysicalAddressCalled() {
+ return mGetPhysicalAddressCalled;
+ }
+
public void setCecVersion(@HdmiControlManager.HdmiCecVersion int cecVersion) {
mCecVersion = cecVersion;
}
@@ -200,6 +206,10 @@
mResultMessages.clear();
}
+ public void clearGetPhysicalAddressCallHistory() {
+ mGetPhysicalAddressCalled = false;
+ }
+
public void setPollAddressResponse(int logicalAddress, int response) {
mPollAddressResponse[logicalAddress] = response;
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 5502de8..0244164 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -472,6 +472,7 @@
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM);
mNativeWrapper.setPhysicalAddress(0x1100);
+ mHdmiControlService.onHotplug(0x1100, true);
assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message))
.isEqualTo(Constants.ABORT_NOT_IN_CORRECT_MODE);
@@ -482,6 +483,8 @@
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM);
mNativeWrapper.setPhysicalAddress(0x1000);
+ mHdmiControlService.onHotplug(0x1000, true);
+
mHdmiCecLocalDeviceAudioSystem.removeAction(ArcInitiationActionFromAvr.class);
assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message))
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 8df7d54..95a7f4b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -167,6 +167,7 @@
}
};
mHdmiCecLocalDevicePlayback.init();
+ mPlaybackPhysicalAddress = 0x2000;
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
hdmiPortInfos[0] =
new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_OUTPUT, 0x0000)
@@ -176,13 +177,12 @@
.build();
mNativeWrapper.setPortInfo(hdmiPortInfos);
mNativeWrapper.setPortConnectionStatus(1, true);
+ mNativeWrapper.setPhysicalAddress(mPlaybackPhysicalAddress);
mHdmiControlService.initService();
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
mHdmiControlService.setPowerManagerInternal(mPowerManagerInternal);
- mPlaybackPhysicalAddress = 0x2000;
- mNativeWrapper.setPhysicalAddress(mPlaybackPhysicalAddress);
mTestLooper.dispatchAll();
mLocalDevices.add(mHdmiCecLocalDevicePlayback);
mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
@@ -416,6 +416,7 @@
int newPlaybackPhysicalAddress = 0x2100;
int switchPhysicalAddress = 0x2000;
mNativeWrapper.setPhysicalAddress(newPlaybackPhysicalAddress);
+ mHdmiControlService.onHotplug(newPlaybackPhysicalAddress, true);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index 192be2a..004c6c6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -181,6 +181,7 @@
mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
mNativeWrapper = new FakeNativeWrapper();
+ mNativeWrapper.setPhysicalAddress(0x2000);
mHdmiCecController = HdmiCecController.createWithNativeWrapper(
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
@@ -199,7 +200,6 @@
mHdmiControlService.initService();
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
- mNativeWrapper.setPhysicalAddress(0x2000);
mTestLooper.dispatchAll();
mNativeWrapper.clearResultMessages();
}
@@ -237,6 +237,7 @@
@Test
public void handleGivePhysicalAddress_success() {
mNativeWrapper.setPhysicalAddress(0x0);
+ mHdmiControlService.onHotplug(0x0, true);
HdmiCecMessage expectedMessage =
HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(ADDR_TV, 0, DEVICE_TV);
@Constants.HandleMessageResult
@@ -252,6 +253,7 @@
@Test
public void handleGivePhysicalAddress_failure() {
mNativeWrapper.setPhysicalAddress(Constants.INVALID_PHYSICAL_ADDRESS);
+ mHdmiControlService.onHotplug(Constants.INVALID_PHYSICAL_ADDRESS, true);
HdmiCecMessage expectedMessage =
HdmiCecMessageBuilder.buildFeatureAbortCommand(
ADDR_TV,
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index b50684b..2a4b797 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -221,13 +221,13 @@
.setEarcSupported(true)
.build();
mNativeWrapper.setPortInfo(hdmiPortInfos);
+ mTvPhysicalAddress = 0x0000;
+ mNativeWrapper.setPhysicalAddress(mTvPhysicalAddress);
mHdmiControlService.initService();
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
- mTvPhysicalAddress = 0x0000;
mEarcBlocksArc = false;
- mNativeWrapper.setPhysicalAddress(mTvPhysicalAddress);
mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED);
mTestLooper.dispatchAll();
mHdmiCecLocalDeviceTv = mHdmiControlService.tv();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
index 1ad9ce0..10f4308 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
@@ -660,4 +660,18 @@
assertThat(mHdmiCecNetwork.getLocalDeviceList().get(0)).isInstanceOf(
HdmiCecLocalDeviceTv.class);
}
+
+ @Test
+ public void portInfoInitiated_getPhysicalAddressCalled_readsFromHalOnFirstCallOnly() {
+ mNativeWrapper.clearGetPhysicalAddressCallHistory();
+ mNativeWrapper.setPhysicalAddress(0x0000);
+ mHdmiCecNetwork.initPortInfo();
+
+ assertThat(mHdmiCecNetwork.getPhysicalAddress()).isEqualTo(0x0000);
+ assertThat(mNativeWrapper.getPhysicalAddressCalled()).isTrue();
+
+ mNativeWrapper.clearGetPhysicalAddressCallHistory();
+ assertThat(mHdmiCecNetwork.getPhysicalAddress()).isEqualTo(0x0000);
+ assertThat(mNativeWrapper.getPhysicalAddressCalled()).isFalse();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
index 9412ee0..3361e7f3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
@@ -115,12 +115,12 @@
.build();
mNativeWrapper.setPortInfo(hdmiPortInfos);
mNativeWrapper.setPortConnectionStatus(1, true);
+ mNativeWrapper.setPhysicalAddress(0x2000);
mHdmiControlService.initService();
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(contextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
mHdmiControlService.getHdmiCecNetwork().initPortInfo();
- mNativeWrapper.setPhysicalAddress(0x2000);
mTestLooper.dispatchAll();
mHdmiCecLocalDevicePlayback = mHdmiControlService.playback();
mHdmiCecPowerStatusController = new HdmiCecPowerStatusController(mHdmiControlService);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index 298ff46..2f4a660 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -118,6 +118,8 @@
mHdmiControlService.setHdmiCecConfig(mHdmiCecConfig);
setHdmiControlEnabled(hdmiControlEnabled);
mNativeWrapper = new FakeNativeWrapper();
+ mPhysicalAddress = 0x2000;
+ mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
mHdmiCecController = HdmiCecController.createWithNativeWrapper(
this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
@@ -127,8 +129,6 @@
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
- mPhysicalAddress = 0x2000;
- mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
mTestLooper.dispatchAll();
mNativeWrapper.clearResultMessages();
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
index 1d4a72f..974f64d 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -111,12 +111,12 @@
.setArcSupported(false)
.build();
mNativeWrapper.setPortInfo(hdmiPortInfo);
+ mPhysicalAddress = 0x0000;
+ mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
mHdmiControlService.initService();
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
- mPhysicalAddress = 0x0000;
- mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
mTestLooper.dispatchAll();
mTvDevice = mHdmiControlService.tv();
mNativeWrapper.clearResultMessages();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
index cafe1e7..f8e465c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -128,6 +128,7 @@
mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
mNativeWrapper = new FakeNativeWrapper();
+ mNativeWrapper.setPhysicalAddress(0x0000);
mHdmiCecController = HdmiCecController.createWithNativeWrapper(
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
@@ -136,7 +137,6 @@
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
- mNativeWrapper.setPhysicalAddress(0x0000);
mTestLooper.dispatchAll();
mHdmiCecLocalDeviceTv = mHdmiControlService.tv();
mTvLogicalAddress = mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java
index 864a182..67a3f2a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java
@@ -87,6 +87,8 @@
mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
mNativeWrapper = new FakeNativeWrapper();
+ mPhysicalAddress = 0x2000;
+ mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(hdmiCecController);
@@ -95,8 +97,6 @@
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
- mPhysicalAddress = 0x2000;
- mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
mTestLooper.dispatchAll();
mPlaybackDevice = mHdmiControlService.playback();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java
index 06709cd..047a04c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java
@@ -89,6 +89,8 @@
mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
mNativeWrapper = new FakeNativeWrapper();
+ mPhysicalAddress = 0x0000;
+ mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(hdmiCecController);
@@ -97,8 +99,6 @@
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
- mPhysicalAddress = 0x0000;
- mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
mTestLooper.dispatchAll();
mTvDevice = mHdmiControlService.tv();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
index 5163e29..1019db4 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
@@ -188,6 +188,7 @@
mHdmiControlService.setIoLooper(mMyLooper);
mNativeWrapper = new FakeNativeWrapper();
+ mNativeWrapper.setPhysicalAddress(0x0000);
mHdmiCecController = HdmiCecController.createWithNativeWrapper(
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
@@ -205,7 +206,6 @@
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
- mNativeWrapper.setPhysicalAddress(0x0000);
mTestLooper.dispatchAll();
mHdmiCecLocalDeviceTv = mHdmiControlService.tv();
mNativeWrapper.clearResultMessages();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
index 4dcc6a4..effea5a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
@@ -92,6 +92,8 @@
mHdmiControlService.setIoLooper(myLooper);
mNativeWrapper = new FakeNativeWrapper();
+ mPhysicalAddress = 0x0000;
+ mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
@@ -115,8 +117,6 @@
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
- mPhysicalAddress = 0x0000;
- mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
mTestLooper.dispatchAll();
mHdmiCecLocalDeviceTv = mHdmiControlService.tv();
mPhysicalAddress = mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress();
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index abd3abe..e64397d 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -372,8 +372,8 @@
doReturn(true)
.when(mWindowManagerInternal)
.setContentRecordingSession(any(ContentRecordingSession.class));
- ContentRecordingSession taskSession =
- createTaskSession(mock(IBinder.class), targetUid);
+ ContentRecordingSession taskSession = createTaskSession(mock(IBinder.class));
+ taskSession.setTargetUid(targetUid);
service.setContentRecordingSession(taskSession);
projection.stop();
@@ -708,8 +708,8 @@
mService =
new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
- ContentRecordingSession taskSession =
- createTaskSession(mock(IBinder.class), targetUid);
+ ContentRecordingSession taskSession = createTaskSession(mock(IBinder.class));
+ taskSession.setTargetUid(targetUid);
mService.setContentRecordingSession(taskSession);
MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
@@ -915,8 +915,8 @@
.setContentRecordingSession(any(ContentRecordingSession.class));
int targetUid = 123455;
- ContentRecordingSession taskSession =
- createTaskSession(mock(IBinder.class), targetUid);
+ ContentRecordingSession taskSession = createTaskSession(mock(IBinder.class));
+ taskSession.setTargetUid(targetUid);
service.setContentRecordingSession(taskSession);
verify(mMediaProjectionMetricsLogger).logInProgress(projection.uid, targetUid);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 70a0038..3da8031 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -2363,8 +2363,20 @@
mAttentionHelper.buzzBeepBlinkLocked(r4, DEFAULT_SIGNALS);
verifyBeepVolume(0.5f);
- verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
- assertNotEquals(-1, r4.getLastAudiblyAlertedMs());
+ // Set important conversation
+ mChannel.setImportantConversation(true);
+ NotificationRecord r5 = getConversationNotificationRecord(mId, false /* insistent */,
+ false /* once */, true /* noisy */, false /* buzzy*/, false /* lights */, true,
+ true, false, null, Notification.GROUP_ALERT_ALL, false, mUser, "yetAnotherPkg",
+ "shortcut");
+
+ // important conversation should beep at 100% volume
+ Mockito.reset(mRingtonePlayer);
+ mAttentionHelper.buzzBeepBlinkLocked(r5, DEFAULT_SIGNALS);
+ verifyBeepVolume(1.0f);
+
+ verify(mAccessibilityService, times(5)).sendAccessibilityEvent(any(), anyInt());
+ assertNotEquals(-1, r5.getLastAudiblyAlertedMs());
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index e564ba6..15c9bfb 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -10696,6 +10696,7 @@
}
@Test
+ @DisableFlags(android.app.Flags.FLAG_REMOVE_REMOTE_VIEWS)
public void testRemoveLargeRemoteViews() throws Exception {
int removeSize = mContext.getResources().getInteger(
com.android.internal.R.integer.config_notificationStripRemoteViewSizeBytes);
@@ -10758,6 +10759,46 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_REMOVE_REMOTE_VIEWS)
+ public void testRemoveRemoteViews() throws Exception {
+ Notification np = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setCustomContentView(mock(RemoteViews.class))
+ .setCustomBigContentView(mock(RemoteViews.class))
+ .setCustomHeadsUpContentView(mock(RemoteViews.class))
+ .build();
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setCustomContentView(mock(RemoteViews.class))
+ .setCustomBigContentView(mock(RemoteViews.class))
+ .setCustomHeadsUpContentView(mock(RemoteViews.class))
+ .setPublicVersion(np)
+ .build();
+
+ assertNotNull(n.contentView);
+ assertNotNull(n.bigContentView);
+ assertNotNull(n.headsUpContentView);
+
+ assertTrue(np.extras.containsKey(Notification.EXTRA_CONTAINS_CUSTOM_VIEW));
+ assertNotNull(np.contentView);
+ assertNotNull(np.bigContentView);
+ assertNotNull(np.headsUpContentView);
+
+ mService.fixNotification(n, mPkg, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
+
+ assertNull(n.contentView);
+ assertNull(n.bigContentView);
+ assertNull(n.headsUpContentView);
+ assertNull(n.publicVersion.contentView);
+ assertNull(n.publicVersion.bigContentView);
+ assertNull(n.publicVersion.headsUpContentView);
+
+ verify(mUsageStats, times(1)).registerImageRemoved(mPkg);
+ }
+
+ @Test
public void testNotificationBubbles_flagAutoExpandForeground_fails_notForeground()
throws Exception {
setUpPrefsForBubbles(mPkg, mUid,
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
index fd69217..2e571bb 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -20,7 +20,7 @@
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ALL_APPS;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ASSIST;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_NOTIFICATION_PANEL;
-import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL;
+import static com.android.server.policy.PhoneWindowManager.SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -288,12 +288,12 @@
}
@Keep
- private static Object[][] shortPressOnSettingsTestArguments() {
- // testName, testKeys, shortPressOnSettingsBehavior, expectedLogEvent, expectedKey,
+ private static Object[][] settingsKeyTestArguments() {
+ // testName, testKeys, settingsKeyBehavior, expectedLogEvent, expectedKey,
// expectedModifierState
return new Object[][]{
{"SETTINGS key -> Toggle Notification panel", new int[]{KeyEvent.KEYCODE_SETTINGS},
- SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL,
+ SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL,
KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_SETTINGS, 0}};
}
@@ -303,7 +303,7 @@
mPhoneWindowManager.overrideKeyEventSource(VENDOR_ID, PRODUCT_ID, DEVICE_BUS);
mPhoneWindowManager.overrideLaunchHome();
mPhoneWindowManager.overrideSearchKeyBehavior(
- PhoneWindowManager.SEARCH_BEHAVIOR_TARGET_ACTIVITY);
+ PhoneWindowManager.SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY);
mPhoneWindowManager.overrideEnableBugReportTrigger(true);
mPhoneWindowManager.overrideStatusBarManagerInternal();
mPhoneWindowManager.overrideStartActivity();
@@ -349,11 +349,11 @@
}
@Test
- @Parameters(method = "shortPressOnSettingsTestArguments")
- public void testShortPressOnSettings(String testName, int[] testKeys,
- int shortPressOnSettingsBehavior, KeyboardLogEvent expectedLogEvent, int expectedKey,
+ @Parameters(method = "settingsKeyTestArguments")
+ public void testSettingsKey(String testName, int[] testKeys,
+ int settingsKeyBehavior, KeyboardLogEvent expectedLogEvent, int expectedKey,
int expectedModifierState) {
- mPhoneWindowManager.overrideShortPressOnSettingsBehavior(shortPressOnSettingsBehavior);
+ mPhoneWindowManager.overrideSettingsKeyBehavior(settingsKeyBehavior);
sendKeyCombination(testKeys, 0 /* duration */);
mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
expectedKey, expectedModifierState, DEVICE_BUS,
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index dd9d05a..fdb57d1 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -496,8 +496,8 @@
mPhoneWindowManager.mDoubleTapOnHomeBehavior = behavior;
}
- void overrideShortPressOnSettingsBehavior(int behavior) {
- mPhoneWindowManager.mShortPressOnSettingsBehavior = behavior;
+ void overrideSettingsKeyBehavior(int behavior) {
+ mPhoneWindowManager.mSettingsKeyBehavior = behavior;
}
void overrideCanStartDreaming(boolean canDream) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index 12c1377..a4bec64 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -57,7 +57,8 @@
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
-public class DesktopModeLaunchParamsModifierTests extends LaunchParamsModifierTestsBase {
+public class DesktopModeLaunchParamsModifierTests extends
+ LaunchParamsModifierTestsBase<DesktopModeLaunchParamsModifier> {
@Before
public void setUp() throws Exception {
mActivity = new ActivityBuilder(mAtm).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsModifierTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsModifierTestsBase.java
index 55f5df1..87671f2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsModifierTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsModifierTestsBase.java
@@ -33,7 +33,7 @@
import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
/** Common base class for launch param modifier unit test classes. */
-public class LaunchParamsModifierTestsBase extends WindowTestsBase{
+public class LaunchParamsModifierTestsBase<T extends LaunchParamsModifier> extends WindowTestsBase {
static final Rect DISPLAY_BOUNDS = new Rect(/* left */ 0, /* top */ 0,
/* right */ 1920, /* bottom */ 1080);
@@ -42,7 +42,7 @@
ActivityRecord mActivity;
- LaunchParamsModifier mTarget;
+ T mTarget;
LaunchParams mCurrent;
LaunchParams mResult;
diff --git a/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java b/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java
index 402b704..78509db 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java
@@ -273,7 +273,7 @@
if (enabled) {
mTransitionController.registerTransitionPlayer(mPlayer, null /* proc */);
} else {
- mTransitionController.detachPlayer();
+ mTransitionController.unregisterTransitionPlayer(mPlayer);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 6b605ec..96ddfe8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -108,6 +108,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.view.InsetsFrameProvider;
@@ -188,6 +189,7 @@
private void setUpApp(DisplayContent display) {
mTask = new TaskBuilder(mSupervisor).setDisplay(display).setCreateActivity(true).build();
mActivity = mTask.getTopNonFinishingActivity();
+ doReturn(false).when(mActivity).isImmersiveMode(any());
}
private void setUpDisplaySizeWithApp(int dw, int dh) {
@@ -396,6 +398,55 @@
verify(translucentActivity.mLetterboxUiController).updateInheritedLetterbox();
}
+ // TODO(b/333663877): Enable test after fix
+ @Test
+ @RequiresFlagsDisabled({Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION})
+ public void testRepositionLandscapeImmersiveAppWithDisplayCutout() {
+ final int dw = 2100;
+ final int dh = 2000;
+ final int cutoutHeight = 150;
+ final TestDisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh)
+ .setCanRotate(false)
+ .setNotch(cutoutHeight)
+ .build();
+ setUpApp(display);
+ display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mWm.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
+ mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true);
+
+ doReturn(true).when(mActivity).isImmersiveMode(any());
+ prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ SCREEN_ORIENTATION_LANDSCAPE);
+ addWindowToActivity(mActivity);
+ mActivity.mRootWindowContainer.performSurfacePlacement();
+
+ final Function<ActivityRecord, Rect> innerBoundsOf =
+ (ActivityRecord a) -> {
+ final Rect bounds = new Rect();
+ a.mLetterboxUiController.getLetterboxInnerBounds(bounds);
+ return bounds;
+ };
+
+ final Consumer<Integer> doubleClick =
+ (Integer y) -> {
+ mActivity.mLetterboxUiController.handleVerticalDoubleTap(y);
+ mActivity.mRootWindowContainer.performSurfacePlacement();
+ };
+
+ final Rect bounds = mActivity.getBounds();
+ assertTrue(bounds.top > cutoutHeight && bounds.bottom < dh);
+ assertEquals(dw, bounds.width());
+
+ // Double click bottom.
+ doubleClick.accept(dh - 10);
+ assertEquals(dh, innerBoundsOf.apply(mActivity).bottom);
+
+ // Double click top.
+ doubleClick.accept(10);
+ doubleClick.accept(10);
+ assertEquals(cutoutHeight, innerBoundsOf.apply(mActivity).top);
+ }
+
@Test
public void testResetOpaqueReferenceWhenOpaqueIsDestroyed() {
mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
@@ -4034,8 +4085,7 @@
// Prepare unresizable landscape activity
prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
- final DisplayPolicy displayPolicy = mActivity.mDisplayContent.getDisplayPolicy();
- doReturn(immersive).when(displayPolicy).isImmersiveMode();
+ doReturn(immersive).when(mActivity).isImmersiveMode(any());
mActivity.mRootWindowContainer.performSurfacePlacement();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 2ce21ba..3c921c6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -70,7 +70,8 @@
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
-public class TaskLaunchParamsModifierTests extends LaunchParamsModifierTestsBase {
+public class TaskLaunchParamsModifierTests extends
+ LaunchParamsModifierTestsBase<TaskLaunchParamsModifier> {
private static final Rect SMALL_DISPLAY_BOUNDS = new Rect(/* left */ 0, /* top */ 0,
/* right */ 1000, /* bottom */ 500);
private static final Rect SMALL_DISPLAY_STABLE_BOUNDS = new Rect(/* left */ 100,
@@ -1898,8 +1899,7 @@
}
Rect startingBounds = new Rect(0, 0, 20, 20);
Rect adjustedBounds = new Rect(startingBounds);
- ((TaskLaunchParamsModifier) mTarget).adjustBoundsToAvoidConflict(displayBounds,
- existingTaskBounds, adjustedBounds);
+ mTarget.adjustBoundsToAvoidConflict(displayBounds, existingTaskBounds, adjustedBounds);
assertEquals(startingBounds, adjustedBounds);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 80c066d..698afaa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1549,8 +1549,10 @@
});
assertTrue(activity1.isVisible());
doReturn(false).when(task1).isTranslucent(null);
+ doReturn(false).when(task1).isTranslucentForTransition();
assertTrue(controller.canApplyDim(task1));
doReturn(true).when(task1).isTranslucent(null);
+ doReturn(true).when(task1).isTranslucentForTransition();
assertFalse(controller.canApplyDim(task1));
controller.finishTransition(closeTransition);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 7e6301f..24ebad6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -795,39 +795,55 @@
}
@Test
- public void testGetTaskWindowContainerTokenForLaunchCookie_nullCookie() {
- WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(null);
- assertThat(wci).isNull();
- }
-
- @Test
- public void testGetTaskWindowContainerTokenForLaunchCookie_invalidCookie() {
+ public void testGetTaskWindowContainerTokenForRecordingSession_invalidCookie() {
Binder cookie = new Binder("test cookie");
- WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie);
+ WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForRecordingSession(
+ ContentRecordingSession.createTaskSession(cookie));
assertThat(wci).isNull();
final ActivityRecord testActivity = new ActivityBuilder(mAtm)
.setCreateTask(true)
.build();
- wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie);
+ wci = mWm.getTaskWindowContainerInfoForRecordingSession(
+ ContentRecordingSession.createTaskSession(cookie));
assertThat(wci).isNull();
}
@Test
- public void testGetTaskWindowContainerTokenForLaunchCookie_validCookie() {
+ public void testGetTaskWindowContainerTokenForRecordingSession_validCookie() {
final Binder cookie = new Binder("ginger cookie");
final WindowContainerToken launchRootTask = mock(WindowContainerToken.class);
final int uid = 123;
setupActivityWithLaunchCookie(cookie, launchRootTask, uid);
- WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie);
+ WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForRecordingSession(
+ ContentRecordingSession.createTaskSession(cookie));
mExpect.that(wci.getToken()).isEqualTo(launchRootTask);
mExpect.that(wci.getUid()).isEqualTo(uid);
}
@Test
- public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies() {
+ public void testGetTaskWindowContainerTokenForRecordingSession_validTaskId() {
+ final WindowContainerToken launchRootTask = mock(WindowContainerToken.class);
+ final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
+ when(remoteToken.toWindowContainerToken()).thenReturn(launchRootTask);
+
+ final int uid = 123;
+ final ActivityRecord testActivity =
+ new ActivityBuilder(mAtm).setCreateTask(true).setUid(uid).build();
+ testActivity.mLaunchCookie = null;
+ testActivity.getTask().mRemoteToken = remoteToken;
+
+ WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForRecordingSession(
+ ContentRecordingSession.createTaskSession(
+ new Binder("cookie"), testActivity.getTask().mTaskId));
+ mExpect.that(wci.getToken()).isEqualTo(launchRootTask);
+ mExpect.that(wci.getUid()).isEqualTo(uid);
+ }
+
+ @Test
+ public void testGetTaskWindowContainerTokenForRecordingSession_multipleCookies() {
final Binder cookie1 = new Binder("ginger cookie");
final WindowContainerToken launchRootTask1 = mock(WindowContainerToken.class);
final int uid1 = 123;
@@ -839,13 +855,14 @@
setupActivityWithLaunchCookie(new Binder("peanut butter cookie"),
mock(WindowContainerToken.class), /* uid= */ 789);
- WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie1);
+ WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForRecordingSession(
+ ContentRecordingSession.createTaskSession(cookie1));
mExpect.that(wci.getToken()).isEqualTo(launchRootTask1);
mExpect.that(wci.getUid()).isEqualTo(uid1);
}
@Test
- public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies_noneValid() {
+ public void testGetTaskWindowContainerTokenForRecordingSession_multipleCookies_noneValid() {
setupActivityWithLaunchCookie(new Binder("ginger cookie"),
mock(WindowContainerToken.class), /* uid= */ 123);
@@ -855,8 +872,8 @@
setupActivityWithLaunchCookie(new Binder("peanut butter cookie"),
mock(WindowContainerToken.class), /* uid= */ 789);
- WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(
- new Binder("some other cookie"));
+ WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForRecordingSession(
+ ContentRecordingSession.createTaskSession(new Binder("some other cookie")));
assertThat(wci).isNull();
}
@@ -895,6 +912,7 @@
public void setContentRecordingSession_sessionContentTask_matchingTask_returnsTrue() {
WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
ActivityRecord activityRecord = createActivityRecord(createTask(mDefaultDisplay));
+ activityRecord.mLaunchCookie = new Binder();
ContentRecordingSession session = ContentRecordingSession.createTaskSession(
activityRecord.mLaunchCookie);
@@ -908,6 +926,7 @@
WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
Task task = createTask(mDefaultDisplay);
ActivityRecord activityRecord = createActivityRecord(task);
+ activityRecord.mLaunchCookie = new Binder();
ContentRecordingSession session =
ContentRecordingSession.createTaskSession(activityRecord.mLaunchCookie);
@@ -915,7 +934,7 @@
mExpect.that(session.getTokenToRecord())
.isEqualTo(task.mRemoteToken.toWindowContainerToken().asBinder());
- mExpect.that(session.getTargetUid()).isEqualTo(activityRecord.getUid());
+ mExpect.that(session.getTargetUid()).isEqualTo(task.effectiveUid);
}
@Test
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 2435243..8fe107c 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -716,6 +716,31 @@
})
public @interface NetCapability { }
+
+ /**
+ * Representing the transport type. Apps should generally not care about transport. A
+ * request for a fast internet connection could be satisfied by a number of different
+ * transports. If any are specified here it will be satisfied a Network that matches
+ * any of them. If a caller doesn't care about the transport it should not specify any.
+ * Must update here when new capabilities are added in {@link NetworkCapabilities}.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "TRANSPORT_" }, value = {
+ NetworkCapabilities.TRANSPORT_CELLULAR,
+ NetworkCapabilities.TRANSPORT_WIFI,
+ NetworkCapabilities.TRANSPORT_BLUETOOTH,
+ NetworkCapabilities.TRANSPORT_ETHERNET,
+ NetworkCapabilities.TRANSPORT_VPN,
+ NetworkCapabilities.TRANSPORT_WIFI_AWARE,
+ NetworkCapabilities.TRANSPORT_LOWPAN,
+ NetworkCapabilities.TRANSPORT_TEST,
+ NetworkCapabilities.TRANSPORT_USB,
+ NetworkCapabilities.TRANSPORT_THREAD,
+ NetworkCapabilities.TRANSPORT_SATELLITE,
+ })
+ public @interface ConnectivityTransport { }
+
+
/**
* Per Android API guideline 8.15, annotation can't be public APIs. So duplicate
* android.net.NetworkAgent.ValidationStatus here. Must update here when new validation status
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
index 379b45c..a23f211 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
@@ -24,6 +24,7 @@
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.traces.parsers.toFlickerComponent
+import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -174,6 +175,12 @@
}
}
+ @FlakyTest(bugId = 342596801)
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
+
@Ignore("Not applicable to this CUJ.")
override fun visibleLayersShownMoreThanOneConsecutiveEntry() {}
diff --git a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/OpenShowWhenLockedSeamlessAppRotationTest.kt b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/OpenShowWhenLockedSeamlessAppRotationTest.kt
new file mode 100644
index 0000000..bf569bc
--- /dev/null
+++ b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/OpenShowWhenLockedSeamlessAppRotationTest.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.wm.flicker.rotation
+
+import android.platform.test.annotations.Presubmit
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.assertions.FlickerTest
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.component.ComponentNameMatcher
+import com.android.server.wm.flicker.BaseTest
+import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test opening an app over lockscreen with rotation change using seamless rotations.
+ */
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenShowWhenLockedSeamlessAppRotationTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
+ val testApp = SeamlessRotationAppHelper(instrumentation)
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ device.sleep()
+ wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+ device.wakeUp()
+ val originalRotation = device.displayRotation
+ ChangeDisplayOrientationRule.setRotation(Rotation.ROTATION_90)
+ Assume.assumeTrue("Assume that lockscreen uses fixed orientation",
+ originalRotation == device.displayRotation)
+ }
+ transitions {
+ // The activity is show-when-locked, so the requested orientation will be changed
+ // from NOSENSOR(keyguard) to UNSPECIFIED(activity). Then the fixed-user-rotation
+ // (by setRotation) will take effect to rotate the display.
+ testApp.launchViaIntent(wmHelper)
+ }
+ teardown { testApp.exit(wmHelper) }
+ }
+
+ @Presubmit
+ @Test
+ fun notContainsRotationAnimation() {
+ flicker.assertLayers {
+ // Verifies that com.android.wm.shell.transition.ScreenRotationAnimation is not used.
+ notContains(ComponentNameMatcher("", "Animation leash of screenshot rotation"))
+ }
+ }
+
+ // Ignore the assertions which are included in SeamlessAppRotationTest.
+ @Test
+ @Ignore("Uninterested")
+ override fun statusBarLayerPositionAtStartAndEnd() {}
+
+ @Test
+ @Ignore("Uninterested")
+ override fun statusBarLayerIsVisibleAtStartAndEnd() {}
+
+ @Test
+ @Ignore("Uninterested")
+ override fun statusBarWindowIsAlwaysVisible() {}
+
+ @Test
+ @Ignore("Uninterested")
+ override fun navBarLayerPositionAtStartAndEnd() {}
+
+ @Test
+ @Ignore("Uninterested")
+ override fun navBarLayerIsVisibleAtStartAndEnd() {}
+
+ @Test
+ @Ignore("Uninterested")
+ override fun navBarWindowIsVisibleAtStartAndEnd() {}
+
+ @Test
+ @Ignore("Uninterested")
+ override fun navBarWindowIsAlwaysVisible() {}
+
+ @Test
+ @Ignore("Uninterested")
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {}
+
+ @Test
+ @Ignore("Uninterested")
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {}
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ // The rotation will be controlled by the setup of test.
+ return LegacyFlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0),
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 3e500d9..45260bd 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -74,6 +74,7 @@
android:taskAffinity="com.android.server.wm.flicker.testapp.SeamlessRotationActivity"
android:theme="@style/CutoutShortEdges"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"
+ android:showWhenLocked="true"
android:label="SeamlessActivity"
android:exported="true">
<intent-filter>
diff --git a/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt b/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt
index 4c1fa6e..4995eeb 100644
--- a/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt
+++ b/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt
@@ -30,7 +30,7 @@
import javax.lang.model.element.AnnotationValue
import javax.lang.model.element.TypeElement
import javax.tools.Diagnostic.Kind
-import javax.tools.StandardLocation.CLASS_OUTPUT
+import javax.tools.StandardLocation.SOURCE_OUTPUT
import kotlin.collections.set
/**
@@ -126,7 +126,7 @@
@Throws(IOException::class)
private fun outputToFile(annotationTypeToIntDefMapping: Map<String, IntDefMapping>) {
val resource = processingEnv.filer.createResource(
- CLASS_OUTPUT, "com.android.winscope", outputName)
+ SOURCE_OUTPUT, "com.android.winscope", outputName)
val writer = resource.openWriter()
serializeTo(annotationTypeToIntDefMapping, writer)
writer.close()
diff --git a/tools/processors/intdef_mappings/test/android/processor/IntDefProcessorTest.kt b/tools/processors/intdef_mappings/test/android/processor/IntDefProcessorTest.kt
index c0c159c..d87b6df 100644
--- a/tools/processors/intdef_mappings/test/android/processor/IntDefProcessorTest.kt
+++ b/tools/processors/intdef_mappings/test/android/processor/IntDefProcessorTest.kt
@@ -24,7 +24,7 @@
import org.junit.Test
import java.io.StringWriter
import javax.tools.JavaFileObject
-import javax.tools.StandardLocation.CLASS_OUTPUT
+import javax.tools.StandardLocation.SOURCE_OUTPUT
/**
* Tests for [IntDefProcessor]
@@ -112,7 +112,7 @@
.compile(filesToCompile.toMutableList())
assertThat(compilation).succeeded()
- assertThat(compilation).generatedFile(CLASS_OUTPUT, "com.android.winscope",
+ assertThat(compilation).generatedFile(SOURCE_OUTPUT, "com.android.winscope",
"intDefMapping.json").contentsAsUtf8String().isEqualTo(expectedFile)
}