diff options
424 files changed, 12793 insertions, 3130 deletions
diff --git a/Ravenwood.bp b/Ravenwood.bp index 412f2b746887..11da20aa6e02 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -284,6 +284,8 @@ android_ravenwood_libgroup { "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 515ddc8d1d49..459c2868e9ba 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.annotation.Nullable; 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 @@ public class BenchmarkRunner { 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 @@ public class BenchmarkRunner { * to avoid unnecessary waiting. */ public void resumeTiming() { - ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers"); + waitCoolDownPeriod(); resumeTimer(); } @@ -162,4 +165,56 @@ public class BenchmarkRunner { } 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 762e2af09cd3..98ab0c290e40 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 @@ public class UserLifecycleTests { } } - /** 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 @@ public class UserLifecycleTests { mRunner.pauseTiming(); Log.d(TAG, "Stopping timer"); removeUser(userId); - waitCoolDownPeriod(); mRunner.resumeTimingForNextIteration(); } } @@ -254,7 +238,6 @@ public class UserLifecycleTests { mRunner.pauseTiming(); final int userId = createUserNoFlags(); - waitForBroadcastIdle(); runThenWaitForBroadcasts(userId, () -> { mRunner.resumeTiming(); Log.i(TAG, "Starting timer"); @@ -309,9 +292,6 @@ public class UserLifecycleTests { preStartUser(userId, numberOfIterationsToSkip); - waitForBroadcastIdle(); - waitCoolDownPeriod(); - runThenWaitForBroadcasts(userId, () -> { mRunner.resumeTiming(); Log.i(TAG, "Starting timer"); @@ -353,9 +333,6 @@ public class UserLifecycleTests { while (mRunner.keepRunning()) { mRunner.pauseTiming(); - waitForBroadcastIdle(); - waitCoolDownPeriod(); - runThenWaitForBroadcasts(userId, () -> { mRunner.resumeTiming(); Log.i(TAG, "Starting timer"); @@ -420,7 +397,6 @@ public class UserLifecycleTests { while (mRunner.keepRunning()) { mRunner.pauseTiming(); - waitCoolDownPeriod(); mRunner.resumeTiming(); Log.i(TAG, "Starting timer"); @@ -454,7 +430,6 @@ public class UserLifecycleTests { mRunner.pauseTiming(); Log.d(TAG, "Stopping timer"); removeUser(userId); - waitCoolDownPeriod(); mRunner.resumeTimingForNextIteration(); } } @@ -466,6 +441,7 @@ public class UserLifecycleTests { mRunner.pauseTiming(); final int startUser = mAm.getCurrentUser(); final int userId = createUserNoFlags(); + mRunner.resumeTiming(); Log.i(TAG, "Starting timer"); @@ -479,27 +455,6 @@ public class UserLifecycleTests { } } - /** 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 @@ public class UserLifecycleTests { mRunner.pauseTiming(); final int startUser = mAm.getCurrentUser(); final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ true); + mRunner.resumeTiming(); Log.i(TAG, "Starting timer"); @@ -536,7 +492,6 @@ public class UserLifecycleTests { while (mRunner.keepRunning()) { mRunner.pauseTiming(); - waitCoolDownPeriod(); Log.d(TAG, "Starting timer"); mRunner.resumeTiming(); @@ -562,7 +517,6 @@ public class UserLifecycleTests { /* useStaticWallpaper */true); while (mRunner.keepRunning()) { mRunner.pauseTiming(); - waitCoolDownPeriod(); Log.d(TAG, "Starting timer"); mRunner.resumeTiming(); @@ -606,7 +560,6 @@ public class UserLifecycleTests { final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ false); while (mRunner.keepRunning()) { mRunner.pauseTiming(); - waitCoolDownPeriod(); Log.d(TAG, "Starting timer"); mRunner.resumeTiming(); @@ -614,7 +567,6 @@ public class UserLifecycleTests { mRunner.pauseTiming(); Log.d(TAG, "Stopping timer"); - waitForBroadcastIdle(); switchUserNoCheck(startUser); mRunner.resumeTimingForNextIteration(); } @@ -631,7 +583,6 @@ public class UserLifecycleTests { /* useStaticWallpaper */ true); while (mRunner.keepRunning()) { mRunner.pauseTiming(); - waitCoolDownPeriod(); Log.d(TAG, "Starting timer"); mRunner.resumeTiming(); @@ -639,7 +590,6 @@ public class UserLifecycleTests { mRunner.pauseTiming(); Log.d(TAG, "Stopping timer"); - waitForBroadcastIdle(); switchUserNoCheck(startUser); mRunner.resumeTimingForNextIteration(); } @@ -675,13 +625,11 @@ public class UserLifecycleTests { @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 @@ public class UserLifecycleTests { 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 @@ public class UserLifecycleTests { 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 @@ public class UserLifecycleTests { switchUser(userId); }, Intent.ACTION_MEDIA_MOUNTED); - waitForBroadcastIdle(); + mRunner.waitCoolDownPeriod(); mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(startUser, () -> { runThenWaitForBroadcasts(userId, () -> { mRunner.resumeTiming(); @@ -781,7 +729,7 @@ public class UserLifecycleTests { switchUser(userId); }, Intent.ACTION_MEDIA_MOUNTED); - waitCoolDownPeriod(); + mRunner.waitCoolDownPeriod(); mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(startUser, () -> { runThenWaitForBroadcasts(userId, () -> { mRunner.resumeTiming(); @@ -827,7 +775,6 @@ public class UserLifecycleTests { Log.d(TAG, "Stopping timer"); attestTrue("Failed creating profile " + userId, mUm.isManagedProfile(userId)); removeUser(userId); - waitCoolDownPeriod(); mRunner.resumeTimingForNextIteration(); } } @@ -868,7 +815,6 @@ public class UserLifecycleTests { mRunner.pauseTiming(); Log.d(TAG, "Stopping timer"); removeUser(userId); - waitCoolDownPeriod(); mRunner.resumeTimingForNextIteration(); } } @@ -913,7 +859,6 @@ public class UserLifecycleTests { mRunner.pauseTiming(); Log.d(TAG, "Stopping timer"); - waitCoolDownPeriod(); mRunner.resumeTimingForNextIteration(); } removeUser(userId); @@ -965,7 +910,6 @@ public class UserLifecycleTests { mRunner.pauseTiming(); Log.d(TAG, "Stopping timer"); removeUser(userId); - waitCoolDownPeriod(); mRunner.resumeTimingForNextIteration(); } } @@ -1030,7 +974,6 @@ public class UserLifecycleTests { mRunner.pauseTiming(); Log.d(TAG, "Stopping timer"); removeUser(userId); - waitCoolDownPeriod(); mRunner.resumeTimingForNextIteration(); } } @@ -1071,7 +1014,6 @@ public class UserLifecycleTests { mRunner.pauseTiming(); Log.d(TAG, "Stopping timer"); removeUser(userId); - waitCoolDownPeriod(); mRunner.resumeTimingForNextIteration(); } } @@ -1124,7 +1066,6 @@ public class UserLifecycleTests { mRunner.pauseTiming(); Log.d(TAG, "Stopping timer"); removeUser(userId); - waitCoolDownPeriod(); mRunner.resumeTimingForNextIteration(); } } @@ -1164,7 +1105,6 @@ public class UserLifecycleTests { runThenWaitForBroadcasts(userId, () -> { startUserInBackgroundAndWaitForUnlock(userId); }, Intent.ACTION_MEDIA_MOUNTED); - waitCoolDownPeriod(); mRunner.resumeTiming(); Log.d(TAG, "Starting timer"); @@ -1280,6 +1220,7 @@ public class UserLifecycleTests { * 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 @@ public class UserLifecycleTests { */ private void stopUserAfterWaitingForBroadcastIdle(int userId) throws RemoteException { - waitForBroadcastIdle(); + mRunner.waitCoolDownPeriod(); stopUser(userId); } @@ -1438,6 +1379,8 @@ public class UserLifecycleTests { */ private void runThenWaitForBroadcasts(int userId, FunctionalUtils.ThrowingRunnable runnable, String... actions) { + mRunner.waitCoolDownPeriod(); + final String unreceivedAction = mBroadcastWaiter.runThenWaitForBroadcasts(userId, runnable, actions); @@ -1538,28 +1481,4 @@ public class UserLifecycleTests { assertEquals("", ShellHelper.runShellCommand("setprop " + name + " " + value)); return TextUtils.firstNotEmpty(oldValue, "invalid"); } - - private void waitForBroadcastIdle() { - try { - ShellHelper.runShellCommandWithTimeout( - "am wait-for-broadcast-idle --flush-broadcast-loopers", TIMEOUT_IN_SECOND); - } catch (TimeoutException e) { - Log.e(TAG, "Ending waitForBroadcastIdle because it is taking too long", e); - } - } - - private void sleep(long ms) { - try { - Thread.sleep(ms); - } catch (InterruptedException e) { - // Ignore - } - } - - private void waitCoolDownPeriod() { - // Heuristic value based on local tests. Stability increased compared to no waiting. - final int tenSeconds = 1000 * 10; - waitForBroadcastIdle(); - sleep(tenSeconds); - } } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 76c1ed619510..7dbf270672f8 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -355,7 +355,7 @@ public final class ActivityThread extends ClientTransactionHandler 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 @@ public final class ActivityThread extends ClientTransactionHandler @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/Notification.java b/core/java/android/app/Notification.java index dfae48b82909..4c839f1762cb 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5637,6 +5637,10 @@ public class Notification implements Parcelable 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 @@ public class Notification implements Parcelable 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 @@ public class Notification implements Parcelable } } - /** 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 193c524e5673..3f6c81b4f53a 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -695,8 +695,8 @@ public final class NotificationChannel implements Parcelable { * {@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 c529f7d95190..44444b5b1471 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -5842,6 +5842,24 @@ public class DevicePolicyManager { * 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 @@ public class DevicePolicyManager { * 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 @@ public class DevicePolicyManager { * 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 86b4180f97c5..7d5806a294e0 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -328,6 +328,16 @@ flag { } 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 6ceae17d05fb..55c3bb60e9c7 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -166,4 +166,11 @@ flag { 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 70c25ea3ab1c..740f5932f902 100644 --- a/core/java/android/app/trust/ITrustManager.aidl +++ b/core/java/android/app/trust/ITrustManager.aidl @@ -41,4 +41,5 @@ interface ITrustManager { 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 23b2ea4dc537..88d4d691cd97 100644 --- a/core/java/android/app/trust/TrustManager.java +++ b/core/java/android/app/trust/TrustManager.java @@ -299,6 +299,20 @@ public class TrustManager { } } + /** + * 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 1eb466cb10a3..e3780edcd7da 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -683,7 +683,9 @@ public final class SQLiteDatabase extends SQLiteClosable { * <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 ec67212b46b7..b2dcf9091198 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -739,6 +739,9 @@ public abstract class DisplayManagerInternal { * 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 @@ public abstract class DisplayManagerInternal { /** 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 c611cb972b2c..3e6223ab9400 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -1948,6 +1948,10 @@ public abstract class BatteryStats { 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 abstract class BatteryStats { 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 abstract class BatteryStats { 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 abstract class BatteryStats { 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 @@ public abstract class BatteryStats { 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 fc3870e812a2..998e295da494 100644 --- a/core/java/android/os/DeadObjectException.java +++ b/core/java/android/os/DeadObjectException.java @@ -26,7 +26,7 @@ import android.os.RemoteException; * 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 @@ import android.os.RemoteException; * * 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 17dfdda7dffc..71957ee3461e 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -52,6 +52,8 @@ import android.util.CloseGuard; 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 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * 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 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * 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 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * @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 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * * @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 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * * @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 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * 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 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * 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 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * 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 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { /** * @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 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * 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 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { /** * @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 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { } } - @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 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { */ @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 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * @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 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * 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 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * @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 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * 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 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { } - @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/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 25389e5ebac4..34fb963924ae 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -193,4 +193,12 @@ flag { 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 ee5e533364ff..fe7eab7abeda 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -81,6 +81,13 @@ flag { } 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/InsetsController.java b/core/java/android/view/InsetsController.java index 1fc98cfa7eb4..a9846fb5751f 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -1262,15 +1262,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation 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 c3e045b305c6..0bdb4ad645f3 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -47,6 +47,7 @@ import android.graphics.Rect; 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 @@ public final class SurfaceControl implements Parcelable { 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, @@ -4304,13 +4305,37 @@ public final class SurfaceControl implements Parcelable { } /** - * 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; } diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java index 97f8084d0031..902968565ac5 100644 --- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java +++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java @@ -68,6 +68,7 @@ public class UnlaunchableAppActivity extends Activity 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 e6af64a3d57f..244165f5e814 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 @@ public class BatteryStatsHistory { } /** + * 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 @@ public class BatteryStatsHistory { } /** + * 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 149442556676..8fe1813b7ba0 100644 --- a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java +++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java @@ -27,6 +27,7 @@ public final class RavenwoodEnvironment { 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 @@ public final class RavenwoodEnvironment { 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 6f1c76385120..8f7026859898 100644 --- a/core/jni/android_database_SQLiteConnection.cpp +++ b/core/jni/android_database_SQLiteConnection.cpp @@ -436,7 +436,7 @@ static jboolean nativeUpdatesTempOnly(JNIEnv* env, jclass, 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 @@ static jboolean nativeUpdatesTempOnly(JNIEnv* env, jclass, // 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) { + // 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); + + if (result != SQLITE_ROW && result != SQLITE_DONE) { 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. - sqlite3_clear_bindings(query); - return count == 0; + 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 2068bd7bc8ea..3006e204a9db 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -1411,8 +1411,10 @@ static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj, 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 23f78084d60c..e831a7d229d6 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -1008,11 +1008,11 @@ static void nativeSetShadowRadius(JNIEnv* env, jclass clazz, jlong transactionOb } 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, @@ -2447,7 +2447,7 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetTransformHint }, {"nativeGetTransformHint", "(J)I", (void*)nativeGetTransformHint }, - {"nativeSetTrustedOverlay", "(JJZ)V", + {"nativeSetTrustedOverlay", "(JJI)V", (void*)nativeSetTrustedOverlay }, {"nativeGetLayerId", "(J)I", (void*)nativeGetLayerId }, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 8541704ecfb9..7b9235cdc691 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 9ad577ad8bf6..85d34e2cf6c6 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 @@ a similar way. <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 0676f721c469..1112e6587961 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 e420ffe68e4c..04f6f5214e74 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 b885e03bc098..a0807ca580a2 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 7e2c111719e5..0d2fd1ca8bf6 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1804,6 +1804,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" /> @@ -1860,7 +1861,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 e118c98dd4da..c4695d95d756 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 @@ public class SQLiteDatabaseTest { assertEquals(3, s.getColumnInt(0)); } + mDatabase.execSQL("DROP TABLE t1"); + } catch (SQLiteException e) { allowed = false; } finally { diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml index 78c88815b46c..297c490f4819 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 1a60c1e45f97..a2082680071a 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 35369c7da6a1..bb2a20157db9 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 1cadfcdf3b81..b96566cea3fa 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 840daabc2ba9..3ad61ca266a2 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 e2fba4ef7741..c644c360f3ca 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 01285e77e3ff..f511c5fca8ce 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 8c911c47dbdf..47302439ef24 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 1817e952ef7e..0bcf9feb7d1f 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 c8a41bc43bea..5b7680db9d9b 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/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml index 782327713fdc..f9fb84d2b31d 100644 --- a/data/etc/preinstalled-packages-platform.xml +++ b/data/etc/preinstalled-packages-platform.xml @@ -134,4 +134,19 @@ to pre-existing users, but cannot uninstall pre-existing system packages from pr <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/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index 67f165016189..08e695b6d6d2 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -92,3 +92,10 @@ flag { 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 35a4a627c0d1..0efdbdc9376c 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.content.res.Resources 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 androidx.test.platform.app.InstrumentationRegistry 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.android.wm.shell.taskview.TaskViewTaskController 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 @@ class BubbleStackViewTest { 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 @@ class BubbleStackViewTest { ) ) positioner = BubblePositioner(context, windowManager) - val bubbleStackViewManager = FakeBubbleStackViewManager() bubbleData = BubbleData( context, @@ -95,8 +103,7 @@ class BubbleStackViewTest { BubbleEducationController(context), shellExecutor ) - - val sysuiProxy = mock<SysuiProxy>() + bubbleStackViewManager = FakeBubbleStackViewManager() expandedViewManager = FakeBubbleExpandedViewManager() bubbleTaskViewFactory = FakeBubbleTaskViewFactory() bubbleStackView = @@ -234,6 +241,115 @@ class BubbleStackViewTest { .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 294b1f0e21fd..0d64527b6c13 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 5d9fe67e8bee..9566f2f140c7 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 4d06dd3b9318..84e144972f38 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 77507a468f94..cf1b8947467e 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 8bc3004a2ba6..f27f46c07de6 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 3244837324b6..f2095b130989 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 @@ public class ShellTaskOrganizer extends TaskOrganizer implements // 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 7749394b21d3..d754d04e6b33 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 @@ package com.android.wm.shell.back; 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 @@ public class BackAnimationBackground { private boolean mIsRequestingStatusBarAppearance; private boolean mBackgroundIsDark; private Rect mStartBounds; + private int mStatusbarHeight; public BackAnimationBackground(RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; @@ -56,9 +55,10 @@ public class BackAnimationBackground { * @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 @@ public class BackAnimationBackground { .show(mBackgroundSurface); mStartBounds = startRect; mIsRequestingStatusBarAppearance = false; + mStatusbarHeight = statusbarHeight; } /** @@ -111,14 +112,14 @@ public class BackAnimationBackground { /** * 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 7c0837eaf2f2..ee740fb70f1c 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.SpringAnimation 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 @@ abstract class CrossActivityBackAnimation( 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 class CrossActivityBackAnimation( 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 @@ abstract class CrossActivityBackAnimation( override fun onConfigurationChanged(newConfiguration: Configuration) { cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context) + statusbarHeight = SystemBarUtils.getStatusBarHeight(context) } override fun getRunner() = backAnimationRunner @@ -182,7 +190,8 @@ abstract class CrossActivityBackAnimation( background.ensureBackground( closingTarget!!.windowConfiguration.bounds, getBackgroundColor(), - transaction + transaction, + statusbarHeight ) ensureScrimLayer() if (isLetterboxed && enteringHasSameLetterbox) { @@ -203,7 +212,6 @@ abstract class CrossActivityBackAnimation( 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 @@ abstract class CrossActivityBackAnimation( enteringTransformation ) applyTransaction() + background.customizeStatusBarAppearance(currentClosingRect.top.toInt()) } private fun getYOffset(centeredRect: RectF, touchY: Float): Float { @@ -255,7 +264,8 @@ abstract class CrossActivityBackAnimation( .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 @@ abstract class CrossActivityBackAnimation( 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 ee898a73a291..381914a58cf2 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.BackProgressAnimator; 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 @@ public class CrossTaskBackAnimation extends ShellBackAnimation { 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 @@ public class CrossTaskBackAnimation extends ShellBackAnimation { @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 @@ public class CrossTaskBackAnimation extends ShellBackAnimation { // 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 @@ public class CrossTaskBackAnimation extends ShellBackAnimation { 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 ab359bdc18b5..c4aafea50a62 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.RootTaskDisplayAreaOrganizer 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 @@ class CustomCrossActivityBackAnimation( 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 @@ class CustomCrossActivityBackAnimation( 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 @@ class CustomCrossActivityBackAnimation( 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 @@ class CustomCrossActivityBackAnimation( 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 @@ class CustomCrossActivityBackAnimation( companion object { private const val PRE_COMMIT_MAX_PROGRESS = 0.2f + private const val MAX_POST_COMMIT_ANIM_DURATION = 2000L } } @@ -226,7 +251,7 @@ class CustomAnimationLoader(private val transitionAnimation: TransitionAnimation // 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 9f07e5b1854a..44752fe0fa72 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 @@ constructor( 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 @@ constructor( 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 42a4ab2e352d..317e00a44bce 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 @@ public class BubbleController implements ConfigurationChangeListener, 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 @@ public class BubbleController implements ConfigurationChangeListener, 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 class BubbleController implements ConfigurationChangeListener, 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 26483c8428c6..874102c20925 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 @@ public class BubbleData { 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 1e482cac0b46..2382545ab324 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 @@ public class BubblePositioner { } /** 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 69bf5fdeecf6..bc63db39e548 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 @@ -3541,7 +3541,7 @@ public class BubbleStackView extends FrameLayout */ 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 95d47146e834..109868daae7d 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.internal.util.FrameworkStatsLog 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 @@ class DesktopModeEventLogger { 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 @@ class DesktopModeEventLogger { 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 @@ class DesktopModeEventLogger { 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 @@ class DesktopModeEventLogger { /* task_y */ taskUpdate.taskY, /* session_id */ - sessionId) + sessionId + ) } /** @@ -95,9 +102,11 @@ class DesktopModeEventLogger { 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 @@ class DesktopModeEventLogger { /* task_y */ taskUpdate.taskY, /* session_id */ - sessionId) + sessionId + ) } /** @@ -124,9 +134,11 @@ class DesktopModeEventLogger { 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 @@ class DesktopModeEventLogger { /* task_y */ taskUpdate.taskY, /* session_id */ - sessionId) + sessionId + ) } companion object { @@ -160,12 +173,8 @@ class DesktopModeEventLogger { * 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 @@ class DesktopModeEventLogger { 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 @@ class DesktopModeEventLogger { * 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 @@ class DesktopModeEventLogger { 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 0b7a3e838e88..5d8e34022841 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 @@ class DesktopModeLoggerTransitionObserver( 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 @@ class DesktopModeLoggerTransitionObserver( 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 f1a475a42452..bc27f341b566 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 android.window.WindowContainerTransaction 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 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl 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 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl 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 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl 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 7e0234ef8546..7d01580ecb6e 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.io.PrintWriter 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 @@ class DesktopModeTaskRepository { 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 @@ class DesktopModeTaskRepository { 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 @@ class DesktopModeTaskRepository { } } - /** - * 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 @@ class DesktopModeTaskRepository { 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 @@ class DesktopModeTaskRepository { 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 @@ class DesktopModeTaskRepository { } } - /** - * 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 @@ class DesktopModeTaskRepository { */ 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 @@ class DesktopModeTaskRepository { 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 @@ class DesktopModeTaskRepository { } } - /** - * 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 @@ class DesktopModeTaskRepository { 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 @@ class DesktopModeTaskRepository { } /** - * 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 @@ class DesktopModeTaskRepository { } } - /** - * 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 @@ class DesktopModeTaskRepository { 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 @@ class DesktopModeTaskRepository { 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 @@ class DesktopModeTaskRepository { * 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 @@ class DesktopModeTaskRepository { * 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 aa11a7d8a663..a9d4e5f3216e 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.internal.logging.UiEventLogger 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 @@ class DesktopModeUiEventLogger @Inject constructor( 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 @@ class DesktopModeUiEventLogger @Inject constructor( } 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 @@ class DesktopModeUiEventLogger @Inject constructor( 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 6da37419737b..217b1d356122 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.os.SystemProperties 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 @@ fun calculateInitialBounds( 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 + maximumSizeMaintainingAspectRatio(taskInfo, idealSize, appAspectRatio) } - } 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) + 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 { - 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) + 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 @@ private fun maximumSizeMaintainingAspectRatio( 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 @@ private fun calculateIdealSize(screenBounds: Rect, scale: Float): Size { 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 2e40ba721ad1..38db1ebdc47b 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 @@ public class DesktopModeVisualIndicator { // 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 @@ public class DesktopModeVisualIndicator { 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 @@ public class DesktopModeVisualIndicator { * 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 @@ public class DesktopModeVisualIndicator { */ 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 e5bf53a4afdb..6e45397411d7 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 @@ import java.util.function.Consumer /** 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 mOnAnimationFinishedCallback = + Consumer<SurfaceControl.Transaction> { t: SurfaceControl.Transaction -> + visualIndicator?.releaseVisualIndicator(t) + visualIndicator = null } - } - private val dragToDesktopStateListener = object : DragToDesktopStateListener { - override fun onCommitToDesktopAnimationStart(tx: SurfaceControl.Transaction) { - removeVisualIndicator(tx) + 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) - } + override fun onCancelToDesktopAnimationEnd(tx: SurfaceControl.Transaction) { + removeVisualIndicator(tx) + } - private fun removeVisualIndicator(tx: SurfaceControl.Transaction) { - visualIndicator?.releaseVisualIndicator(tx) - visualIndicator = null + 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( /** 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( } /** - * 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 @@ class DesktopTasksController( */ 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 @@ class DesktopTasksController( } /** - * 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 @@ class DesktopTasksController( } /** - * 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 @@ class DesktopTasksController( /** 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 @@ class DesktopTasksController( splitScreenController.getStageOfTask(taskInfo.taskId), EXIT_REASON_DESKTOP_MODE ) - splitScreenController.transitionHandler - ?.onSplitToDesktop() + splitScreenController.transitionHandler?.onSplitToDesktop() } } @@ -469,16 +464,16 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( * 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 @@ class DesktopTasksController( KtProtoLog.v( WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d taskDisplayId=%d", - taskId, + taskId, task.displayId ) @@ -560,7 +555,7 @@ class DesktopTasksController( KtProtoLog.v( WM_SHELL_DESKTOP_MODE, "moveToDisplay: taskId=%d displayId=%d", - task.taskId, + task.taskId, displayId ) @@ -585,9 +580,9 @@ class DesktopTasksController( } /** - * 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 @@ class DesktopTasksController( // 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( } 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 @@ class DesktopTasksController( } 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( } // 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( * 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( } 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 @@ class DesktopTasksController( // 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( ) { 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 @@ class DesktopTasksController( * 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 @@ class DesktopTasksController( /** 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( /** * 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( } 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( */ 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 @@ class DesktopTasksController( } } - /** - * 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 @@ class DesktopTasksController( * @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 @@ class DesktopTasksController( } // 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 @@ class DesktopTasksController( @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 @@ class DesktopTasksController( } override fun addDesktopGestureExclusionRegionListener( - listener: Consumer<Region>, - callbackExecutor: Executor + listener: Consumer<Region>, + callbackExecutor: Executor ) { mainExecutor.execute { this@DesktopTasksController.setTaskRegionListener(listener, callbackExecutor) @@ -1339,21 +1349,15 @@ class DesktopTasksController( } 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( } 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 @@ class DesktopTasksController( 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 e5e435da48b2..98c79d7174a9 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 @@ import java.util.function.Supplier * 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 @@ class DragToDesktopTransitionHandler( * 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) - - transitionState = if (isSplitTask(taskId)) { - val otherTask = getOtherSplitTask(taskId) ?: throw IllegalStateException( - "Expected split task to have a counterpart." - ) - TransitionState.FromSplit( + 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( draggedTaskId = taskId, dragAnimator = dragToDesktopAnimator, startTransitionToken = startTransitionToken, otherSplitTask = otherTask - ) - } else { - TransitionState.FromFullscreen( + ) + } else { + TransitionState.FromFullscreen( draggedTaskId = taskId, dragAnimator = dragToDesktopAnimator, startTransitionToken = startTransitionToken - ) - } + ) + } } /** @@ -216,15 +218,16 @@ class DragToDesktopTransitionHandler( } 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 @@ class DragToDesktopTransitionHandler( 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 @@ class DragToDesktopTransitionHandler( 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 @@ class DragToDesktopTransitionHandler( } 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 @@ class DragToDesktopTransitionHandler( 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 @@ class DragToDesktopTransitionHandler( 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 @@ class DragToDesktopTransitionHandler( } override fun handleRequest( - transition: IBinder, - request: TransitionRequestInfo + transition: IBinder, + request: TransitionRequestInfo ): WindowContainerTransaction? { // Only handle transitions started from shell. return null @@ -489,8 +508,8 @@ class DragToDesktopTransitionHandler( 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 @@ class DragToDesktopTransitionHandler( 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 @@ class DragToDesktopTransitionHandler( // 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 @@ class DragToDesktopTransitionHandler( 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 @@ class DragToDesktopTransitionHandler( 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 @@ class DragToDesktopTransitionHandler( interface DragToDesktopStateListener { fun onCommitToDesktopAnimationStart(tx: SurfaceControl.Transaction) + fun onCancelToDesktopAnimationEnd(tx: SurfaceControl.Transaction) } @@ -601,31 +628,32 @@ class DragToDesktopTransitionHandler( 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 74b8f831cdc0..526cf4d0295b 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 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); private OnTaskResizeAnimationListener mOnTaskResizeAnimationListener; + public EnterDesktopTaskTransitionHandler( Transitions transitions) { this(transitions, SurfaceControl.Transaction::new); @@ -72,11 +73,12 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition } 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 7342bd1ae5de..9f9e256fc2b7 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 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH 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 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH /** * 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 c469e652b117..88d0554669b7 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 @@ class ToggleResizeDesktopTaskTransitionHandler( .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 e0e2e706d649..7d2aa275a684 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 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, 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 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, 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 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, 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 c53e7fe00598..d8f2c02b5399 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 @@ public class RecentTasksController implements TaskStackListenerCallback, 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 6adbe4f7ce92..456658c54fd0 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 @@ public class PerfettoTransitionTracer implements TransitionTracer { 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/MaximizeButtonView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt index 53bdda192757..78f0ef7af45c 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.view.View 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 @@ class MaximizeButtonView( 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 @@ class MaximizeButtonView( } } - @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 1c4b742b6fb2..3c12da2d6620 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.Bitmap 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 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( 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 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( 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 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( 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 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( context.withStyledAttributes(null, intArrayOf(attr), 0, 0) { return getColor(0, 0) } - return Color.BLACK + return Color.WHITE + } + + @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) } - data class Header( + private data class Header( val type: Type, val systemTheme: Theme, val appTheme: Theme, @@ -408,7 +528,7 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( 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 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( 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 f2f10aef4fd7..d03d7799d675 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 @@ class FromSplitScreenEnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : 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 82c070cbf1c3..f9b4108bc8c2 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_DISMISSED; 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 androidx.test.filters.SmallTest; 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 @@ import java.util.Optional; * Tests for the shell task organizer. * * Build/Install/Run: - * atest WMShellUnitTests:ShellTaskOrganizerTests + * atest WMShellUnitTests:ShellTaskOrganizerTests */ @SmallTest @RunWith(AndroidJUnit4.class) @@ -92,6 +94,8 @@ public class ShellTaskOrganizerTests extends ShellTestCase { private ShellExecutor mTestExecutor; @Mock private ShellCommandHandler mShellCommandHandler; + @Mock + private RecentTasksController mRecentTasksController; private ShellTaskOrganizer mOrganizer; private ShellInit mShellInit; @@ -120,6 +124,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase { 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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { } } - @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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { @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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { @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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { @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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { @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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { @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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { @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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { @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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { @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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { @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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { @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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { // 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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { boolean gotException = false; try { - mOrganizer.addListenerForTaskId(task1Listener, 1); + mOrganizer.addListenerForTaskId(task1Listener, /* taskId= */ 1); } catch (Exception e) { gotException = true; } @@ -343,26 +348,27 @@ public class ShellTaskOrganizerTests extends ShellTestCase { @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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { @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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { @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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { @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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { 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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { 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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { 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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { 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 @@ public class ShellTaskOrganizerTests extends ShellTestCase { @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 8f59f30da697..310ccc252469 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 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { @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 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { @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 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { 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 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { @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 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { 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 d8d534bec6ea..ac67bd1fedd8 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 @@ class DesktopTasksControllerTest : ShellTestCase() { 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 3c488cac6edd..77f917cc28d8 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 @@ class DesktopTasksLimiterTest : ShellTestCase() { 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 cd68c6996578..3f3cafcf6375 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 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onFocusTaskChanged(task); - verify(mDesktopModeTaskRepository).addOrMoveFreeformTaskToTop(task.taskId); + verify(mDesktopModeTaskRepository) + .addOrMoveFreeformTaskToTop(task.displayId, task.taskId); } @Test @@ -103,7 +104,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase { 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 884cb6ec9f74..56c4ceacc8ab 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 @@ public class RecentTasksControllerTest extends ShellTestCase { mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener); ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10); - mRecentTasksControllerReal.onTaskWindowingModeChanged(taskInfo); + mRecentTasksControllerReal.onTaskRunningInfoChanged(taskInfo); verify(mRecentTasksListener).onRunningTaskChanged(taskInfo); } @@ -525,7 +525,7 @@ public class RecentTasksControllerTest extends ShellTestCase { 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 63b45387f040..386a606c61c6 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -8617,6 +8617,7 @@ public class AudioManager { @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 e612645fb4d7..c8b9da50b082 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -142,7 +142,7 @@ interface IAudioService { @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 838630fcccb9..0589c0f12ab6 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -523,7 +523,7 @@ public final class MediaRoute2Info implements Parcelable { 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 @@ public final class MediaRoute2Info implements Parcelable { } 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 @@ public final class MediaRoute2Info implements Parcelable { @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 @@ public final class MediaRoute2Info implements Parcelable { 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 @@ public final class MediaRoute2Info implements Parcelable { 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 e62d112a7ccb..8fa0e49e8b96 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -402,9 +402,6 @@ public final class MediaRouter2Manager { @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 @@ public final class MediaRouter2Manager { 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 a3c8b689f0a6..a2a16ef5230b 100644 --- a/media/java/android/media/RoutingSessionInfo.java +++ b/media/java/android/media/RoutingSessionInfo.java @@ -107,7 +107,7 @@ public final class RoutingSessionInfo implements Parcelable { @NonNull final List<String> mTransferableRoutes; - final int mVolumeHandling; + @MediaRoute2Info.PlaybackVolume final int mVolumeHandling; final int mVolumeMax; final int mVolume; @@ -207,9 +207,10 @@ public final class RoutingSessionInfo implements Parcelable { 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 @@ public final class RoutingSessionInfo implements Parcelable { 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 @@ public final class RoutingSessionInfo implements Parcelable { @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 @@ public final class RoutingSessionInfo implements Parcelable { 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; diff --git a/media/java/android/media/audiopolicy/AudioVolumeGroup.java b/media/java/android/media/audiopolicy/AudioVolumeGroup.java index d60712674990..0f5fbcc25f09 100644 --- a/media/java/android/media/audiopolicy/AudioVolumeGroup.java +++ b/media/java/android/media/audiopolicy/AudioVolumeGroup.java @@ -31,6 +31,7 @@ import com.android.internal.util.Preconditions; 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 @@ public final class AudioVolumeGroup implements Parcelable { && 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/packages/InputDevices/res/raw/keyboard_layout_serbian_latin.kcm b/packages/InputDevices/res/raw/keyboard_layout_serbian_latin.kcm new file mode 100644 index 000000000000..0ff1dea91d10 --- /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 e10bd7f9bf3e..be7d2c11a8f9 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 d4f8f7de23e7..84e4b9e14c55 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/view/accessibility/data/repository/CaptioningRepository.kt b/packages/SettingsLib/src/com/android/settingslib/view/accessibility/data/repository/CaptioningRepository.kt index 5bcb82d6c579..0b71d257d562 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.StateFlow 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 @@ class CaptioningRepositoryImpl( 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 3b8433316b5d..8204569ce2f8 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.Flow 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 @@ interface AudioRepository { 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 @@ class AudioRepositoryImpl( audioManagerEventsReceiver.events .filterIsInstance(AudioManagerEvent.InternalRingerModeChanged::class) .map { RingerMode(audioManager.ringerModeInternal) } + .onStart { emit(RingerMode(audioManager.ringerModeInternal)) } .flowOn(backgroundCoroutineContext) .stateIn( coroutineScope, @@ -122,6 +142,7 @@ class AudioRepositoryImpl( } .filterNotNull() .map { audioManager.communicationDevice } + .onStart { emit(audioManager.communicationDevice) } .flowOn(backgroundCoroutineContext) .stateIn( coroutineScope, @@ -130,17 +151,18 @@ class AudioRepositoryImpl( ) 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 @@ class AudioRepositoryImpl( // 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 870068087058..683759db95f9 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.Test 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 @@ class AudioRepositoryTest { @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 @@ class AudioRepositoryTest { 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 @@ class AudioRepositoryTest { AudioRepositoryImpl( eventsReceiver, audioManager, + contentResolver, testScope.testScheduler, testScope.backgroundScope, ) @@ -254,6 +261,12 @@ class AudioRepositoryTest { 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 374240bb7262..04d30ed6f001 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 ee81813b4245..0dd9275503a9 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.UserHandle; 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 @@ public final class RingtonePickerActivity extends AlertActivity implements } 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 ac680a97cf3a..e940674a1cf5 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -752,6 +752,7 @@ android_robolectric_test { plugins: [ "dagger2-compiler", ], + strict_mode: false, } // in-place tests which use Robolectric in the tests directory @@ -771,7 +772,6 @@ android_robolectric_test { ], static_libs: [ "RoboTestLibraries", - "mockito-kotlin2", ], libs: [ "android.test.runner", @@ -787,6 +787,7 @@ android_robolectric_test { plugins: [ "dagger2-compiler", ], + strict_mode: false, } android_ravenwood_test { diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 163c8493e4d9..780a099350d0 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -964,6 +964,9 @@ flag { 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 f5d01d70e077..907c39d842ce 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 @@ private class AnimatedDialog( } 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 cc55df129c33..8e824e60d449 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 @@ class TransitionAnimator( 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) - - if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) { - openingWindowSyncViewOverlay?.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) } } } 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 b05729669f7c..536f2972abdd 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 @@ fun BackAnimationSpec.Companion.floatingSystemSurfacesForSysUi( 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 49d1fb423d2b..029f62c6e4c9 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 @@ data class BackTransformation( 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 000000000000..b1945a1c37db --- /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 99f7d0f902a6..887e3494b49e 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.compose.modifiers.sysuiResTag 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 cf2e895b044b..e6132c6a2a59 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 @@ fun SceneScope.NotificationScrollingStack( viewModel: NotificationsPlaceholderViewModel, maxScrimTop: () -> Float, shouldPunchHoleBehindScrim: Boolean, + shouldFillMaxSize: Boolean = true, shadeMode: ShadeMode, modifier: Modifier = Modifier, ) { @@ -327,14 +328,14 @@ fun SceneScope.NotificationScrollingStack( } 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 e17a1464a71a..ae53d56e331a 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 @@ import kotlinx.coroutines.flow.StateFlow 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/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt index 04f76f5d58ab..4d946bff63ca 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.scene.ui.composable.ComposableScene 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 92b2b4e886b9..18e655085ae6 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.Alignment 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 @@ fun SceneContainer( 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 cbaa89438f2e..f5a0ef2adde7 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.Scenes 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 @@ val SceneContainerTransitions = transitions { // 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 @@ val SceneContainerTransitions = transitions { 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 000000000000..48ec198a790a --- /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 000000000000..225ca4ebdb91 --- /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 000000000000..372e4a1af482 --- /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 000000000000..ce24f5ed6592 --- /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 000000000000..a6b268d59bf4 --- /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 000000000000..2baaecf47ec5 --- /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 2f1108593d2f..34cc6769eb40 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.Box 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 androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexScenePicker import com.android.compose.animation.scene.SceneScope +import com.android.compose.modifiers.thenIf +import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.keyguard.ui.composable.LockscreenContent import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel @@ -54,6 +59,8 @@ fun SceneScope.OverlayShade( 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 @@ fun SceneScope.OverlayShade( 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 @@ private fun SceneScope.Panel( } } +@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 OverlayShade { 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/statusbar/phone/EdgeToEdgeDialogDelegate.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/EdgeToEdgeDialogDelegate.kt index 55dfed407e7a..5fc78c07033e 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 @@ class EdgeToEdgeDialogDelegate : DialogDelegate<SystemUIDialog> { 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 83b8158a9bcf..ab14911ab425 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.fillMaxWidth 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 @@ private val padding = 24.dp 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 b2424f42bf43..ade5171d6415 100644 --- a/packages/SystemUI/docs/demo_mode.md +++ b/packages/SystemUI/docs/demo_mode.md @@ -22,42 +22,45 @@ intent. <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 -e level 4 ``` +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 d88260f0760a..303548131788 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 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { @Test fun canShowAlternateBouncerForFingerprint_alternateBouncerUIUnavailable() { givenCanShowAlternateBouncer() - bouncerRepository.setAlternateBouncerUIAvailable(false) + kosmos.keyguardBouncerRepository.setAlternateBouncerUIAvailable(false) assertFalse(underTest.canShowAlternateBouncerForFingerprint()) } @@ -116,7 +119,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { @Test fun canShowAlternateBouncerForFingerprint_ifFingerprintIsNotUsuallyAllowed() { givenCanShowAlternateBouncer() - biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) assertFalse(underTest.canShowAlternateBouncerForFingerprint()) } @@ -124,7 +127,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { @Test fun canShowAlternateBouncerForFingerprint_strongBiometricNotAllowed() { givenCanShowAlternateBouncer() - biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(false) + kosmos.biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(false) assertFalse(underTest.canShowAlternateBouncerForFingerprint()) } @@ -132,23 +135,24 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { @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 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { @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 givenCanShowAlternateBouncer() { + private fun givenAlternateBouncerSupported() { if (DeviceEntryUdfpsRefactor.isEnabled) { - fingerprintPropertyRepository.supportsUdfps() + kosmos.fingerprintPropertyRepository.supportsUdfps() } else { - bouncerRepository.setAlternateBouncerUIAvailable(true) + kosmos.keyguardBouncerRepository.setAlternateBouncerUIAvailable(true) } - bouncerRepository.setPrimaryShow(false) - biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) - biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true) - whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false) - whenever(keyguardStateController.isUnlocked).thenReturn(false) + } + + private fun givenCanShowAlternateBouncer() { + 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 2d78a9b9d808..45e7d8a43c2a 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 @@ class CommunalRepositoryImplTest : SysuiTestCase() { 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 5a7cbf6e02ca..6b896c68857f 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.ext.junit.runners.AndroidJUnit4 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 @@ class CommunalInteractorCommunalDisabledTest : SysuiTestCase() { 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 83227e1fc597..12389a3861bb 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.broadcast.broadcastDispatcher 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.fakeCommunalRepository 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 @@ class CommunalInteractorTest : SysuiTestCase() { 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 @@ class CommunalInteractorTest : SysuiTestCase() { } @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 @@ class CommunalInteractorTest : SysuiTestCase() { 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 @@ class CommunalInteractorTest : SysuiTestCase() { 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 @@ class CommunalInteractorTest : SysuiTestCase() { 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 @@ class CommunalInteractorTest : SysuiTestCase() { // 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 @@ class CommunalInteractorTest : SysuiTestCase() { 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 @@ class CommunalInteractorTest : SysuiTestCase() { 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 be44339bab8d..6b807754dead 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.compose.animation.scene.ObservableTransitionState 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 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { 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 eef23372cad2..933f0952dd09 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.ScrimController 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.Mock 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 @@ class DreamOverlayServiceTest : SysuiTestCase() { @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 000000000000..460a1fc44e67 --- /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 20ffa3312fa6..33e2cac948e2 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.compose.animation.scene.ObservableTransitionState 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 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() 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/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt index 288c0832170f..db5921d8bd36 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 @@ class GroupExpansionManagerTest : SysuiTestCase() { 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 1cdf8dc4a8e8..79b5cc37119f 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 com.google.common.truth.Truth.assertThat; 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.Dependency; 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 @@ public class SystemUIDialogTest extends SysuiTestCase { 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 fdea5a4bd765..254a967e480e 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 androidx.test.filters.SmallTest 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 @@ class BottomBarViewModelTest : SysuiTestCase() { @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 @@ class BottomBarViewModelTest : SysuiTestCase() { underTest.onDoneClicked() runCurrent() - val volumePanelState by collectLastValue(volumePanelViewModel.volumePanelState) - assertThat(volumePanelState!!.isVisible).isFalse() + assertThat(volumePanelGlobalStateRepository.globalState.value.isVisible).isFalse() } } } @@ -106,8 +108,7 @@ class BottomBarViewModelTest : SysuiTestCase() { .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 000000000000..d44724f67457 --- /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 b37184dc941c..d712afee8c74 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 @@ class DefaultComponentsLayoutManagerTest : SysuiTestCase() { 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 @@ class DefaultComponentsLayoutManagerTest : SysuiTestCase() { 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 f6ada4c166dc..420b955e88e0 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.Kosmos 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 @@ import org.junit.runner.RunWith 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 @@ class VolumePanelViewModelTest : SysuiTestCase() { @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 @@ class VolumePanelViewModelTest : SysuiTestCase() { @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 @@ class VolumePanelViewModelTest : SysuiTestCase() { 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 6e6e032ef2c5..c83b6d38a0e1 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 000000000000..1d9307ba20ed --- /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 000000000000..77172ca8f90e --- /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 8e1d0a57100c..2bf6f80c32d0 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 704cf0b61b1b..12e226a47bb5 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 02b74ce14088..7d7a5d4dbf14 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 abfdc2a79603..6f2806d80ef3 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 3f34df73c662..3bf3fb3a1ebd 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java @@ -300,6 +300,7 @@ public class CarrierTextManager { }); mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener); cancelSatelliteCollectionJob(/* reason= */ "Starting new job"); + mLogger.logStartListeningForSatelliteCarrierText(); mSatelliteConnectionJob = mJavaAdapter.alwaysCollectFlow( mDeviceBasedSatelliteViewModel.getCarrierText(), @@ -316,7 +317,7 @@ public class CarrierTextManager { mWakefulnessLifecycle.removeObserver(mWakefulnessObserver); }); mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener); - cancelSatelliteCollectionJob(/* reason= */ "Stopping listening"); + cancelSatelliteCollectionJob(/* reason= */ "#handleSetListening has null callback"); } } @@ -336,6 +337,7 @@ public class CarrierTextManager { private void onSatelliteCarrierTextChanged(@Nullable String text) { mLogger.logUpdateCarrierTextForReason(REASON_SATELLITE_CHANGED); + mLogger.logNewSatelliteCarrierText(text); mSatelliteCarrierText = text; updateCarrierText(); } @@ -654,6 +656,7 @@ public class CarrierTextManager { 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 48fea55e7503..7d0c49144219 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 @@ class CarrierTextManagerLogger @Inject constructor(@CarrierTextManagerLog val bu 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 @@ class CarrierTextManagerLogger @Inject constructor(@CarrierTextManagerLog val bu ) } + 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 @@ class CarrierTextManagerLogger @Inject constructor(@CarrierTextManagerLog val bu 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 ce4032aaea05..bebfd859f9ed 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 @@ constructor( ) } + 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 @@ constructor( ) } - fun logDropNonFingerprintMessage( + fun logDropFaceMessage( message: CharSequence, followUpMessage: CharSequence?, - biometricSourceType: BiometricSourceType?, ) { buffer.log( KeyguardIndicationController.TAG, @@ -129,12 +144,8 @@ constructor( { 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 7b5a09cb3848..28dd2338ff2b 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.provider.Settings; 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 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, } 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 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, 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 2e29c3b59c4c..7503a8b4362d 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 @@ object UdfpsTouchOverlayBinder { 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 49d0847ab0c7..51b228026b03 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 open class AvailableMediaDeviceItemFactory : DeviceItemFactory() { } } -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 e0334a060ee2..1d11dfbc48a8 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.KeyguardInteractor 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 @@ constructor( 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 @@ constructor( 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 1de3459d8f7d..7f137f3b976b 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 @@ import dagger.Module @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 index 8bfd8d91dfca..d6d08b4f1208 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.flowOf 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 @@ interface CommunalRepository { /** 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 @@ interface CommunalRepository { @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 @@ constructor( 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 9599a8864bcc..2be28caa71a9 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.compose.animation.scene.TransitionKey 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 @@ constructor( @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 @@ constructor( 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 @@ constructor( * * 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 @@ constructor( * * 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 @@ constructor( * 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 @@ constructor( } } } - -/** 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 000000000000..5cfe9798420d --- /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 000000000000..e3187c2d7e27 --- /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 a3c61a413639..73cfb5286e1c 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 @@ import com.android.compose.animation.scene.TransitionKey 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 b7e8205e6582..058ca4d963a0 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 @@ class CommunalAppWidgetHost( 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 @@ class CommunalAppWidgetHost( 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/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt index ec574d2d031d..a5eafa9d9025 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 com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticati 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 @@ constructor( 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 9cdba5853959..f2a544ece2cb 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.internal.policy.IKeyguardStateCallback; 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 kotlinx.coroutines.CoroutineScope; 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 @@ public class KeyguardService extends Service { 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 @@ public class KeyguardService extends Service { } private final WindowManagerOcclusionManager mWmOcclusionManager; + private final KeyguardEnabledInteractor mKeyguardEnabledInteractor; private final Lazy<FoldGracePeriodProvider> mFoldGracePeriodProvider = new Lazy<>() { @Override @@ -331,7 +336,9 @@ public class KeyguardService extends Service { 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 @@ public class KeyguardService extends Service { mFlags = featureFlags; mPowerInteractor = powerInteractor; mSceneInteractorLazy = sceneInteractorLazy; + mMainExecutor = mainExecutor; if (KeyguardWmStateRefactor.isEnabled()) { WindowManagerLockscreenVisibilityViewBinder.bind( @@ -355,6 +363,7 @@ public class KeyguardService extends Service { } mWmOcclusionManager = windowManagerOcclusionManager; + mKeyguardEnabledInteractor = keyguardEnabledInteractor; } @Override @@ -593,6 +602,7 @@ public class KeyguardService extends Service { public void setKeyguardEnabled(boolean enabled) { trace("setKeyguardEnabled enabled" + enabled); checkPermission(); + mKeyguardEnabledInteractor.notifyKeyguardEnabled(enabled); mKeyguardViewMediator.setKeyguardEnabled(enabled); } @@ -619,8 +629,8 @@ public class KeyguardService extends Service { 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 0ebc92ed1c03..b1589dadb300 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.CoroutineScope 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 @@ interface DeviceEntryFingerprintAuthRepository { */ 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 @@ constructor( 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 8a53dd18541c..a2bbcadbf8c7 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 @@ interface KeyguardRepository { ) 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 @@ interface KeyguardRepository { "'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 @@ constructor( 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 @@ constructor( _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 a306954b7d8f..01109af79c1d 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.app.tracing.coroutines.launch 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 @@ constructor( private val keyguardInteractor: KeyguardInteractor, powerInteractor: PowerInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, + val deviceEntryRepository: DeviceEntryRepository, ) : TransitionInteractor( fromState = KeyguardState.AOD, @@ -125,7 +127,12 @@ constructor( 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 115fc3610ac8..7d3de306d621 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.communal.domain.interactor.CommunalInteractor 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 @@ constructor( powerInteractor: PowerInteractor, private val communalInteractor: CommunalInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, + val deviceEntryRepository: DeviceEntryRepository, ) : TransitionInteractor( fromState = KeyguardState.DOZING, @@ -99,7 +101,9 @@ constructor( 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 @@ constructor( !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 @@ constructor( 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 2b3732f75812..8ca29c80c2e9 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.Background 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 kotlin.time.Duration.Companion.milliseconds 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 @@ constructor( 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 @@ constructor( 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 fcf67d519cae..af1ce2bfcdde 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 @@ package com.android.systemui.keyguard.domain.interactor 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 @@ constructor( if (id == null) { // No transition started. if ( - transitionProgress is CommunalTransitionProgress.Transition && + transitionProgress is CommunalTransitionProgressModel.Transition && lastStartedState == fromState ) { transitionId = @@ -93,7 +93,7 @@ constructor( 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 @@ constructor( 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 000000000000..8dede01cd20b --- /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 8ba09bd2e766..fb65a6dacbe3 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 @@ constructor( 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 d8b7b4a6a3d4..e92dec0c7163 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 @@ package com.android.systemui.keyguard.shared.model 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 @@ import com.android.systemui.biometrics.shared.model.AuthenticationReason /** * 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 @@ data class AcquiredFingerprintAuthenticationStatus( } /** 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 @@ data class ErrorFingerprintAuthenticationStatus( 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 200d30c66305..7a2e61049ea7 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 @@ constructor( } } + /** + * 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 0aa6d129f247..0f1f5c1f1cb5 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 @@ constructor( } } - 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 @@ constructor( } } + 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 5babc8b63abf..14890d7031f0 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 @@ public class LogModule { @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/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 94870853f1be..d0f8412c85b2 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.classifier.Classifier.BACK_GESTURE; 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.SystemClock; 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.List; 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 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack 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 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack 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 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack @Override public void onInputDeviceRemoved(int deviceId) { - if (mTrackpadDeviceId == deviceId) { - update(false /* isTrackpadConnected */); + mTrackpadsConnected.remove(deviceId); + if (mTrackpadsConnected.isEmpty()) { + update(); + } + } + + 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 void update(boolean isTrackpadConnected) { - boolean isPreviouslyTrackpadConnected = mIsTrackpadConnected; - mIsTrackpadConnected = isTrackpadConnected; - if (isPreviouslyTrackpadConnected != mIsTrackpadConnected) { - 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 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack mOverviewProxyService.removeCallback(mQuickSwitchListener); mSysUiState.removeCallback(mSysUiStateCallback); mInputManager.unregisterInputDeviceListener(mInputDeviceListener); + mTrackpadsConnected.clear(); updateIsEnabled(); mUserTracker.removeCallback(mUserChangedCallback); } @@ -605,7 +621,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack Trace.beginSection("EdgeBackGestureHandler#updateIsEnabled"); mIsGestureHandlingEnabled = mInGestureNavMode || (mUsingThreeButtonNav - && mIsTrackpadConnected); + && !mTrackpadsConnected.isEmpty()); boolean isEnabled = mIsAttached && mIsGestureHandlingEnabled; if (isEnabled == mIsEnabled) { return; @@ -867,15 +883,6 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack 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 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack // 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 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack 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/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt index d161c6b834c0..7b679932de3e 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.InfiniteGridConsistencyI 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.GridLayout 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 @@ interface PanelsModule { 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 @@ interface PanelsModule { } @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 000000000000..686e5f49442b --- /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 000000000000..a871531f283a --- /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 000000000000..c92234c3e535 --- /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 f5ee720faff6..4aeaa7d23771 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.Modifier 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 @@ import javax.inject.Inject 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 @@ constructor( 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 @@ constructor( } ) { 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 @@ constructor( 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 8d0b386bd817..708ef0dd7ff6 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.border 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.rememberScrollState 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.remember 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 com.android.systemui.res.R 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( Tile( tile = smallTiles[index], iconOnly = true, - modifier = Modifier.height(tileHeight) + showLabels = showLabels, + modifier = Modifier.height(iconTileHeight) ) } } @@ -101,8 +103,9 @@ constructor( 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 @@ constructor( } 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 @@ constructor( @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 @@ constructor( 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 @@ constructor( @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 @@ constructor( 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 ddd97c2e8944..70d629fa7c70 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.Modifier 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 @@ import javax.inject.Inject 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 @@ constructor( // 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 @@ constructor( 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 @@ constructor( 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 e8c65a5ac78a..a6838c0c06a2 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.contentDescription 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 @@ object TileType fun Tile( tile: TileViewModel, iconOnly: Boolean, + showLabels: Boolean = false, modifier: Modifier, ) { val state: TileUiState by @@ -136,7 +140,8 @@ fun Tile( secondaryLabel = state.secondaryLabel.toString(), icon = icon, colors = state.colors, - iconOnly = iconOnly + iconOnly = iconOnly, + showLabels = showLabels, ) } } @@ -213,6 +218,7 @@ fun LazyGridScope.editTiles( clickAction: ClickAction, onClick: (TileSpec) -> Unit, isIconOnly: (TileSpec) -> Boolean, + showLabels: Boolean = false, indicatePosition: Boolean = false, ) { items( @@ -250,10 +256,13 @@ fun LazyGridScope.editTiles( 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 Badge(action: ClickAction, modifier: Modifier = Modifier) { fun EditTile( tileViewModel: EditTileViewModel, iconOnly: Boolean, + showLabels: Boolean, modifier: Modifier = Modifier, ) { val label = tileViewModel.label.load() ?: tileViewModel.tileSpec.spec @@ -297,6 +307,7 @@ fun EditTile( colors = colors, icon = tileViewModel.icon, iconOnly = iconOnly, + showLabels = showLabels, animateIconToEnd = true, ) } @@ -380,9 +391,26 @@ private fun TileContent( 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 @@ private fun TileContent( } } } + +@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 000000000000..5d4b8f1773f2 --- /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 000000000000..9ad00c8d3cfa --- /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 000000000000..a4ee58f0963c --- /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 000000000000..730cf635972d --- /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/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt index b971781acd63..bccbb1130bcc 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 @@ package com.android.systemui.scene.ui.view 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 @@ class SceneWindowRootView( 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/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 4c60090743fd..01adab306ed1 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -223,7 +223,8 @@ public class ScreenshotController { 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; @@ -672,7 +673,7 @@ public class ScreenshotController { () -> { 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 @@ public class ScreenshotController { new ViewTreeObserver.OnWindowAttachListener() { @Override public void onWindowAttached() { - mBlockAttach = false; + mAttachRequested = false; decorView.getViewTreeObserver().removeOnWindowAttachListener(this); action.run(); } @@ -703,13 +704,13 @@ public class ScreenshotController { @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 @@ public class ScreenshotController { Log.d(TAG, "Removing screenshot window"); } mWindowManager.removeViewImmediate(decorView); + mDetachRequested = false; + } + if (mAttachRequested && !mDetachRequested) { + mDetachRequested = true; + withWindowAttached(this::removeWindow); } mViewProxy.stopInputListening(); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt index 3ac070a28b2b..1b5fa345ebb4 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.app.Notification 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.ScreenshotShelfView 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( ) } + 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 a0c939107fdb..da2024b4ef18 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 @@ abstract class ShadeModule { 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 4b6dd8d00ae7..5e4b1732bd42 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 @@ public class ShadeCarrierGroupController { 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 @@ public class ShadeCarrierGroupController { ActivityStarter activityStarter, @Background Handler bgHandler, @Main Looper mainLooper, + ShadeCarrierGroupControllerLogger logger, NetworkController networkController, CarrierTextManager.Builder carrierTextManagerBuilder, Context context, @@ -160,6 +163,7 @@ public class ShadeCarrierGroupController { mContext = context; mActivityStarter = activityStarter; mBgHandler = bgHandler; + mLogger = logger; mNetworkController = networkController; mStatusBarPipelineFlags = statusBarPipelineFlags; mCarrierTextManager = carrierTextManagerBuilder @@ -374,10 +378,13 @@ public class ShadeCarrierGroupController { 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 @@ public class ShadeCarrierGroupController { } } } 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 @@ public class ShadeCarrierGroupController { 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 @@ public class ShadeCarrierGroupController { ActivityStarter activityStarter, @Background Handler handler, @Main Looper looper, + ShadeCarrierGroupControllerLogger logger, NetworkController networkController, CarrierTextManager.Builder carrierTextControllerBuilder, Context context, @@ -484,6 +495,7 @@ public class ShadeCarrierGroupController { mActivityStarter = activityStarter; mHandler = handler; mLooper = looper; + mLogger = logger; mNetworkController = networkController; mCarrierTextControllerBuilder = carrierTextControllerBuilder; mContext = context; @@ -505,6 +517,7 @@ public class ShadeCarrierGroupController { 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 000000000000..36aa7a6127a9 --- /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 000000000000..af06a356002b --- /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 d7f96e6cfa6b..ea2f9ab817e4 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 53c10a370868..fe16fc03ce40 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( .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 @@ constructor( } } .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 8d8a36acc2a2..47939ae07539 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.SysUISingleton; 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 @@ public class KeyguardIndicationController { 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 @@ public class KeyguardIndicationController { 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 @@ public class KeyguardIndicationController { FeatureFlags flags, IndicationHelper indicationHelper, KeyguardInteractor keyguardInteractor, - BiometricMessageInteractor biometricMessageInteractor + BiometricMessageInteractor biometricMessageInteractor, + DeviceEntryFingerprintAuthInteractor deviceEntryFingerprintAuthInteractor, + DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor ) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; @@ -325,6 +339,8 @@ public class KeyguardIndicationController { mIndicationHelper = indicationHelper; mKeyguardInteractor = keyguardInteractor; mBiometricMessageInteractor = biometricMessageInteractor; + mDeviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor; + mDeviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor; mFaceAcquiredMessageDeferral = faceHelpMessageDeferral.create(); @@ -409,6 +425,8 @@ public class KeyguardIndicationController { collectFlow(mIndicationArea, mBiometricMessageInteractor.getCoExFaceAcquisitionMsgIdsToShow(), mCoExAcquisitionMsgIdsToShowCallback); + collectFlow(mIndicationArea, mDeviceEntryFingerprintAuthInteractor.isEngaged(), + mIsFingerprintEngagedCallback); } /** @@ -944,19 +962,25 @@ public class KeyguardIndicationController { 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 @@ public class KeyguardIndicationController { @Override public void onTrustAgentErrorMessage(CharSequence message) { - showBiometricMessage(message, null); + showTrustAgentErrorMessage(message); } @Override @@ -1467,6 +1491,10 @@ public class KeyguardIndicationController { hideBiometricMessage(); mBiometricErrorMessageToShowOnScreenOn = null; } + + if (!running && biometricSourceType == FACE) { + showTrustAgentErrorMessage(mTrustAgentErrorMessage); + } } @Override @@ -1533,6 +1561,25 @@ public class KeyguardIndicationController { 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 @@ public class KeyguardIndicationController { new KeyguardStateController.Callback() { @Override public void onUnlockedChanged() { + mTrustAgentErrorMessage = null; updateDeviceEntryIndication(false); } @@ -1649,6 +1697,7 @@ public class KeyguardIndicationController { 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/chips/call/domain/interactor/CallChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt index e85df0eb9a70..c57cf69a5d79 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 @@ package com.android.systemui.statusbar.chips.call.domain.interactor 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 70362c8527f4..c3d37fb3d952 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 index e63713bf1602..c7539181fe31 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 6f16969b1bb6..bff5686641c2 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 47b2b0346d4a..208eb50487e3 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 @@ package com.android.systemui.statusbar.chips.ui.viewmodel 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 8505c5ff32e0..0d5ade7ab49c 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 @@ constructor(context: Context, gestureDetector: GesturePointerEventDetector) : Co 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 2fd0a5324d15..0ece88dcd184 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 @@ abstract class SwipeUpGestureHandler( 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 87f11f131f32..938a71fd7b82 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.statusbar.policy.HeadsUpManager 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 PeekPackageSnoozedSuppressor(private val headsUpManager: HeadsUpManager) : } } -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 9c6a42384a53..74925c8dd506 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.statusbar.policy.KeyguardStateController; 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 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter 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 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter DeviceProvisionedController deviceProvisionedController, SystemClock systemClock, GlobalSettings globalSettings, - EventLog eventLog) { + EventLog eventLog, + Optional<Bubbles> bubbles) { mPowerManager = powerManager; mBatteryController = batteryController; mAmbientDisplayConfiguration = ambientDisplayConfiguration; @@ -148,6 +152,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter mSystemClock = systemClock; mGlobalSettings = globalSettings; mEventLog = eventLog; + mBubbles = bubbles; ContentObserver headsUpObserver = new ContentObserver(mainHandler) { @Override public void onChange(boolean selfChange) { @@ -440,7 +445,9 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter } 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 f68e194aace2..7e16cd5a693f 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.EventLog 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 @@ constructor( 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 @@ constructor( 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 61373815db1c..c2ce1144fe1b 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.domain.interactor.SceneInteractor 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 com.android.systemui.util.kotlin.FlowDumperImpl 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 @@ constructor( } 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 @@ constructor( /** 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 @@ constructor( 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 25d1f05316db..2beb66b57672 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 @@ package com.android.systemui.statusbar.phone 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 @@ interface DialogDelegate<T : Dialog> { 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/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java index 11feb971db6d..f99a81e43797 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 class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar 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 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar @Override public void onHeadsUpPinned(NotificationEntry entry) { - updateTopEntry(); + updateTopEntry("onHeadsUpPinned"); updateHeader(entry); updateHeadsUpAndPulsingRoundness(entry); } @@ -231,7 +231,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar mPhoneStatusBarTransitions.onHeadsUpStateChanged(isHeadsUp); } - private void updateTopEntry() { + private void updateTopEntry(String reason) { NotificationEntry newEntry = null; if (shouldBeVisible()) { newEntry = mHeadsUpManager.getTopEntry(); @@ -370,7 +370,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar @Override public void onHeadsUpUnPinned(NotificationEntry entry) { - updateTopEntry(); + updateTopEntry("onHeadsUpUnPinned"); updateHeader(entry); updateHeadsUpAndPulsingRoundness(entry); } @@ -388,7 +388,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar updateHeadsUpHeaders(); } if (isExpanded() != oldIsExpanded) { - updateTopEntry(); + updateTopEntry("setAppearFraction"); } } @@ -462,11 +462,11 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar } 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 68457ea45531..ffc859eb9197 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 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements 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 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements 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 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements } else if (mTrackingHeadsUp) { mEntriesToRemoveAfterExpand.add(entry); } else { - removeEntry(entry.getKey()); + removeEntry(entry.getKey(), "createRemoveRunnable"); } }; } @@ -661,7 +661,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements } } 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 f767262dbd4e..6d4301f8475f 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 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat * 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 c74dde57b5f5..e01556f91fac 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 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh 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 12f252d215a9..9d9cc2aa11de 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.SatelliteSupp 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.delay 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 @@ sealed interface SatelliteSupport { @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 @@ class DeviceBasedSatelliteRepositoryImpl @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 @@ constructor( } } + /** + * 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 @@ constructor( 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 332c1210f8cc..d76fd40522e4 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 @@ package com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel 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.combine 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 @@ interface DeviceBasedSatelliteViewModel { } @OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton class DeviceBasedSatelliteViewModelImpl @Inject constructor( @@ -124,18 +127,37 @@ constructor( 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 8b48bd32e089..21691540fc56 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( showNext() runnable.run() } else { - log { "$fn => [removing untracked ${getKey(entry)}]" } + log { "$fn => removing untracked ${getKey(entry)}" } } logState("after $fn") } @@ -239,6 +242,18 @@ constructor( 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 a7fe49b54602..2ee98bb61d80 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 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { */ @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 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { 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 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { 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 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { } 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 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { 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 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { * 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 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { } 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 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { 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 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { 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 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { /** 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 11cbc9c66923..6ffb162ad0f2 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 @@ class HeadsUpManagerLogger @Inject constructor( }) } - 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 @@ class HeadsUpManagerLogger @Inject constructor( }) } - 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 @@ class HeadsUpManagerLogger @Inject constructor( }) } - 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 f4fc978b9b91..1ae5614ae4b6 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.AudioModeInteractor 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 @@ interface AudioModule { 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 @@ interface AudioModule { 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 ea67eea1dd8b..73f52373f893 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 android.view.accessibility.CaptioningManager 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 @@ interface CaptioningModule { companion object { @Provides + @SysUISingleton fun provideCaptioningRepository( captioningManager: CaptioningManager, @Background coroutineContext: CoroutineContext, @@ -41,6 +43,7 @@ interface CaptioningModule { 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 369610882959..efab199424ca 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 @@ package com.android.systemui.volume.dagger 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 @@ interface MediaDevicesModule { @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 4ba7cbb1588c..a11997a969d7 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 android.media.Spatializer 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 @@ interface SpatializerModule { 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 e052f243f7ea..0dc264781070 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 @@ package com.android.systemui.volume.panel.component.mediaoutput.data.repository 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 @@ interface LocalMediaRepositoryFactory { 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 000000000000..e46ce2699beb --- /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 000000000000..e930aca05505 --- /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 000000000000..fac49db5c755 --- /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 f57e2931c9b3..dc43155e6b1c 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 @@ import android.content.res.Configuration.Orientation 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 a30de1b89695..f495a02f6cc7 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.VolumePanelComponent 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 @@ class VolumePanelViewModel( daggerComponentFactory: VolumePanelComponentFactory, configurationController: ConfigurationController, broadcastDispatcher: BroadcastDispatcher, + private val volumePanelGlobalStateInteractor: VolumePanelGlobalStateInteractor, ) { private val volumePanelComponent: VolumePanelComponent = @@ -72,18 +72,12 @@ class VolumePanelViewModel( 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 @@ class VolumePanelViewModel( SharingStarted.Eagerly, VolumePanelState( orientation = resources.configuration.orientation, - isVisible = mutablePanelVisibility.value, isLargeScreen = resources.getBoolean(R.bool.volume_panel_is_large_screen) ), ) @@ -126,7 +119,7 @@ class VolumePanelViewModel( } fun dismissPanel() { - mutablePanelVisibility.update { false } + volumePanelGlobalStateInteractor.setVisible(false) } class Factory @@ -136,6 +129,7 @@ class VolumePanelViewModel( 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 @@ class VolumePanelViewModel( 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 905a74976bdf..03c8af9020e1 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 @@ constructor( 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 @@ constructor( } 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) + } + + private fun createNewVolumePanelDialog(): Dialog { + return dialogFactory.createBottomSheet( + content = { dialog -> + LaunchedEffect(dialog) { + dialog.setOnDismissListener { + uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_GONE) + volumePanelGlobalStateInteractor.setVisible(false) + } + } - VolumePanelRoot( - viewModel = viewModelFactory.create(rememberCoroutineScope()), - onDismiss = { dialog.dismiss() }, - ) - }, + 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 190babdb22b0..0ed84ea2d183 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.util.DisplayMetrics 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 @@ class BackAnimationSpecTest : SysuiTestCase() { 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 @@ private fun assertBackTransformation( ) 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 190b3d25d16b..44a546704953 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 androidx.test.filters.SmallTest 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 @@ class BackTransformationTest : SysuiTestCase() { } @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 @@ class BackTransformationTest : SysuiTestCase() { 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 472d0458b77a..8be1e7a2b3f1 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 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { { 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 fa3fe5c80adb..e02fb29d1070 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.bouncer.data.repository.fakeKeyguardBouncerRepositor 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 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest .startedTransition( to = KeyguardState.LOCKSCREEN, from = KeyguardState.DOZING, - ownerName = "FromDozingTransitionInteractor", animatorAssertion = { it.isNotNull() } ) @@ -600,7 +600,6 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest .startedTransition( to = KeyguardState.PRIMARY_BOUNCER, from = KeyguardState.DOZING, - ownerName = "FromDozingTransitionInteractor", animatorAssertion = { it.isNotNull() } ) @@ -618,6 +617,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest // 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 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest .startedTransition( to = KeyguardState.GONE, from = KeyguardState.DOZING, - ownerName = "FromDozingTransitionInteractor", animatorAssertion = { it.isNotNull() } ) @@ -683,7 +682,6 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest .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/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt index 49a467e152ec..1ef44f74e8b3 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.TouchHandler 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 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { 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 4c2d90845771..5363c57c1bad 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 android.widget.TextView; 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 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { 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 fe066ca2c318..59678a2f8c90 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 android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT; 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.AlternateBouncerInteractor 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 @@ public class KeyguardIndicationControllerBaseTest extends SysuiTestCase { @Mock protected BiometricMessageInteractor mBiometricMessageInteractor; @Mock + protected DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor; + @Mock + protected DeviceEntryFingerprintAuthInteractor mDeviceEntryFingerprintAuthInteractor; + @Mock protected ScreenLifecycle mScreenLifecycle; @Mock protected AuthController mAuthController; @@ -237,6 +248,7 @@ public class KeyguardIndicationControllerBaseTest extends SysuiTestCase { .thenReturn(mock(StateFlow.class)); when(mFaceHelpMessageDeferralFactory.create()).thenReturn(mFaceHelpMessageDeferral); + when(mDeviceEntryFingerprintAuthInteractor.isEngaged()).thenReturn(mock(StateFlow.class)); mIndicationHelper = new IndicationHelper(mKeyguardUpdateMonitor); @@ -279,7 +291,9 @@ public class KeyguardIndicationControllerBaseTest extends SysuiTestCase { mFlags, mIndicationHelper, KeyguardInteractorFactory.create(mFlags).getKeyguardInteractor(), - mBiometricMessageInteractor + mBiometricMessageInteractor, + mDeviceEntryFingerprintAuthInteractor, + mDeviceEntryFaceAuthInteractor ); mController.init(); mController.setIndicationArea(mIndicationArea); @@ -306,4 +320,22 @@ public class KeyguardIndicationControllerBaseTest extends SysuiTestCase { 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 cfe9e2ab7bb6..80011dcab1cd 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 @@ public class KeyguardIndicationControllerTest extends KeyguardIndicationControll } @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 @@ public class KeyguardIndicationControllerTest extends KeyguardIndicationControll 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 4a14f8853904..a68ba06637cc 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 android.view.View 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 @@ class KeyguardIndicationControllerWithCoroutinesTest : KeyguardIndicationControl 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 000000000000..25efaf10fce7 --- /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 fa2b343c3f3d..1260f07d0ba1 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.coroutines.collectLastValue 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 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { @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 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { @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 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { 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 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { 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 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { 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 0906d8eadf44..9f752a89b16d 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 androidx.test.filters.SmallTest 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 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() { 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 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() { 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 3e8461a225b2..bfe5c6e233d3 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.graphics.drawable.Icon; 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.statusbar.policy.KeyguardStateController; 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 org.mockito.MockitoAnnotations; import java.util.Arrays; import java.util.HashSet; +import java.util.Optional; import java.util.Set; /** @@ -127,6 +128,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { UserTracker mUserTracker; @Mock DeviceProvisionedController mDeviceProvisionedController; + @Mock + Bubbles mBubbles; FakeSystemClock mSystemClock; FakeGlobalSettings mGlobalSettings; FakeEventLog mEventLog; @@ -137,6 +140,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { 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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { mDeviceProvisionedController, mSystemClock, mGlobalSettings, - mEventLog); + mEventLog, + Optional.of(mBubbles)); mNotifInterruptionStateProvider.mUseHeadsUp = true; } @@ -170,7 +175,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { * {@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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @Test - public void testShouldHeadsUpAwake() throws RemoteException { + public void testShouldHeadsUpAwake() { ensureStateForHeadsUpWhenAwake(); NotificationEntry entry = createNotification(IMPORTANCE_HIGH); @@ -216,7 +221,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @Test - public void testShouldHeadsUp() throws RemoteException { + public void testShouldHeadsUp() { ensureStateForHeadsUpWhenAwake(); NotificationEntry entry = createNotification(IMPORTANCE_HIGH); @@ -327,7 +332,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { * 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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } /** + * 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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { * {@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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { * 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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { * 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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @Test - public void testShouldHeadsUp_oldWhen_whenNow() throws Exception { + public void testShouldHeadsUp_oldWhen_whenNow() { ensureStateForHeadsUpWhenAwake(); NotificationEntry entry = createNotification(IMPORTANCE_HIGH); @@ -458,7 +479,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @Test - public void testShouldHeadsUp_oldWhen_whenRecent() throws Exception { + public void testShouldHeadsUp_oldWhen_whenRecent() { ensureStateForHeadsUpWhenAwake(); NotificationEntry entry = createNotification(IMPORTANCE_HIGH); @@ -472,7 +493,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { @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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @Test - public void testShouldHeadsUp_oldWhen_whenNegative() throws Exception { + public void testShouldHeadsUp_oldWhen_whenNegative() { ensureStateForHeadsUpWhenAwake(); NotificationEntry entry = createNotification(IMPORTANCE_HIGH); @@ -499,7 +520,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @Test - public void testShouldHeadsUp_oldWhen_hasFullScreenIntent() throws Exception { + public void testShouldHeadsUp_oldWhen_hasFullScreenIntent() { ensureStateForHeadsUpWhenAwake(); long when = makeWhenHoursAgo(25); @@ -514,7 +535,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @Test - public void testShouldHeadsUp_oldWhen_isForegroundService() throws Exception { + public void testShouldHeadsUp_oldWhen_isForegroundService() { ensureStateForHeadsUpWhenAwake(); long when = makeWhenHoursAgo(25); @@ -529,7 +550,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @Test - public void testShouldNotHeadsUp_oldWhen() throws Exception { + public void testShouldNotHeadsUp_oldWhen() { ensureStateForHeadsUpWhenAwake(); long when = makeWhenHoursAgo(25); @@ -543,7 +564,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @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 a6177e8feb1b..6c7a95fc7fd0 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.NotificationInte 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 @@ class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecision 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 eeb51a684d42..7903a731c1d0 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.collection.NotificationEntry 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 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro 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 71e7dc522e20..a45740502012 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.notification.interruption.NotificationInte 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.FakeBatteryController 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 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { 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 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { deviceProvisionedController.currentUser = userId userTracker.set(listOf(user), /* currentUserIndex = */ 0) systemSettings = FakeSettings() + whenever(bubbles.canShowBubbleNotification()).thenReturn(true) provider.start() } @@ -208,6 +211,14 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } @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 61c008b55ad0..01e638b0ab17 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.EventLog 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 @@ object VisualInterruptionDecisionProviderTestUtil { avalancheProvider: AvalancheProvider, systemSettings: SystemSettings, packageManager: PackageManager, + bubbles: Optional<Bubbles>, ): VisualInterruptionDecisionProvider { return if (VisualInterruptionRefactor.isEnabled) { VisualInterruptionDecisionProviderImpl( @@ -75,7 +78,8 @@ object VisualInterruptionDecisionProviderTestUtil { userTracker, avalancheProvider, systemSettings, - packageManager + packageManager, + bubbles ) } else { NotificationInterruptStateProviderWrapper( @@ -95,7 +99,8 @@ object VisualInterruptionDecisionProviderTestUtil { 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 cde241bbe918..62804ed1412e 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.init.NotificationsController; 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.KeyguardStateController; 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.concurrency.MessageRouterImpl; 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 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mFakeGlobalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON); + when(mBubbles.canShowBubbleNotification()).thenReturn(true); + mVisualInterruptionDecisionProvider = VisualInterruptionDecisionProviderTestUtil.INSTANCE.createProviderByFlag( mAmbientDisplayConfiguration, @@ -395,7 +393,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mUserTracker, mAvalancheProvider, mSystemSettings, - mPackageManager); + mPackageManager, + Optional.of(mBubbles)); mVisualInterruptionDecisionProvider.start(); mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class)); @@ -1418,46 +1417,4 @@ public class CentralSurfacesImplTest extends SysuiTestCase { 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 7ca3b1c425d3..6300953c86b7 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 @@ class DeviceBasedSatelliteRepositorySwitcherTest : SysuiTestCase() { 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 6b0ad4bdb770..66516769c804 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 @@ package com.android.systemui.statusbar.pipeline.satellite.data.prod 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 androidx.test.filters.SmallTest 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.Mock 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 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { 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 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { underTest = DeviceBasedSatelliteRepositoryImpl( Optional.empty(), + telephonyManager, dispatcher, testScope.backgroundScope, FakeLogBuffer.Factory.create(), @@ -362,6 +368,68 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { 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 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { 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 7b0a55608970..0f89dcc42c82 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.FakeEventLog; 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 @@ public class BubblesTest extends SysuiTestCase { 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 @@ public class BubblesTest extends SysuiTestCase { 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 c9964c233d44..000000000000 --- 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 070a3697df68..f75cdd4d3bbc 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.keyguard.domain.interactor.keyguardTransitionInterac 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 @@ var Kosmos.alternateBouncerInteractor by 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 482d60ce8adf..c7955c3e244b 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.Kosmos.Fixture 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 index d958bae7ae2a..a7bf87def5be 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.flatMapLatest 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 3fe6973d36df..1583d1c5d83f 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 android.os.userManager 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 @@ val Kosmos.communalInteractor by Fixture { 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 000000000000..ee48c105a987 --- /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 93e0b418d076..d558c9654d9c 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 @@ class FakeDeviceEntryFingerprintAuthRepository @Inject constructor() : 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 96a40494b11a..1a45c4298691 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 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { 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 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { _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 bbe37c18dd08..42af25ed51a7 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 @@ val Kosmos.fromAodTransitionInteractor by 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 23dcd965c028..edf77a007efd 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 @@ var Kosmos.fromDozingTransitionInteractor by 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 604d9e435e8e..4039ee6ea904 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 @@ package com.android.systemui.keyguard.domain.interactor 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 @@ val Kosmos.fromGoneTransitionInteractor by 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 7eef704c1622..be8048e774f6 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.keyguard.data.repository.FakeTrustRepository 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 @@ object KeyguardDismissInteractorFactory { { 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 000000000000..0667a6b9536a --- /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/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 000000000000..277dbb7016ad --- /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 000000000000..7b9e4a17e998 --- /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 34b266a54f41..82cfaf50f823 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 @@ package com.android.systemui.qs.panels.domain.interactor 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 4febfe9160ab..37c9552ded44 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 @@ package com.android.systemui.qs.panels.domain.interactor 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 000000000000..daf6087c2d6f --- /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 000000000000..89b42a69c326 --- /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 000000000000..f6dfb8bcd47b --- /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 000000000000..b07cc7d8612d --- /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/statusbar/chips/ui/viewmodel/FakeOngoingActivityChipInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/FakeOngoingActivityChipInteractor.kt index cd08274c5f98..90d459b5d3c8 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 ffbaa7ff3528..9e02df9c3f41 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 0e909c498a2b..f19ac1e5a58d 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 000000000000..2ba1211a9bdb --- /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 000000000000..6e523648c2af --- /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 a60658830be4..34a008f92518 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.kosmos.testScope 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 @@ val Kosmos.volumePanelViewModelFactory: VolumePanelViewModel.Factory by 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 63b3f237a953..c3300f570dc2 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.plugins.activityStarter 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 @@ val Kosmos.volumeNavigator by volumePanelViewModelFactory, systemUIDialogFactory, uiEventLoggerFake, + volumePanelGlobalStateInteractor, ) } diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index 95cbb6b2130a..48bc803f4a5c 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -56,11 +56,52 @@ java_library { 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 @@ java_library { srcs: [ "runtime-helper-src/framework/**/*.java", ], + static_libs: [ + "ravenwood-runtime-common", + ], libs: [ "framework-minus-apex.ravenwood", "ravenwood-junit", @@ -105,6 +149,7 @@ java_library { ], static_libs: [ "androidx.test.monitor-for-device", + "ravenwood-runtime-common", ], libs: [ "android.test.mock", @@ -145,6 +190,10 @@ java_library { "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 @@ cc_library_shared { ], 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 5506a46203d6..49e793fcbddf 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 @@ public class RavenwoodRuleImpl { 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 9d12f855c0ea..68b5aebe9c00 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.DisabledOnRavenwood; 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 @@ import java.util.regex.Pattern; * 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 c3786ee0041d..5f1b0c2c929f 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 @@ public class RavenwoodSystemProperties { 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 99ab32788235..19c1bffaebcd 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 @@ public class RavenwoodUtils { 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 @@ public class RavenwoodUtils { * 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 773a89a3df4d..483b98a96034 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.runner.Description; 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 000000000000..171471684371 --- /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 000000000000..59f474a39033 --- /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 000000000000..ee280991216a --- /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 000000000000..9aedaab5b911 --- /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 000000000000..61d54cba8b54 --- /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 000000000000..c8cc8d9fe273 --- /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 000000000000..7b0cebcecc1a --- /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 000000000000..65402219ebee --- /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 2d799142df70..8fe6853abb45 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_WORLD_WRITEABLE; 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 @@ public class ParcelFileDescriptor_host { 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 68bf92273022..b00cee02f611 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.platform.test.ravenwood.RavenwoodSystemProperties; 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 @@ public class RavenwoodEnvironment_host { 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 @@ public class RavenwoodEnvironment_host { 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 69ff262fe915..e198646d4e27 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 @@ public class ClassLoadHook { 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 8e3a21dd6d87..000000000000 --- 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 388156aa3694..843455d060c9 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 000000000000..e031eb27513b --- /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 000000000000..c56ec8a60f00 --- /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 839b62a39471..000000000000 --- 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 93861e87318f..14b5a4f0c1e0 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 @@ import java.lang.ref.Reference; * (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 @@ public class NativeAllocationRegistry { } final Runnable releaser = () -> { - applyFreeFunction(mFreeFunction, nativePtr); + RavenwoodRuntimeNative.applyFreeFunction(mFreeFunction, nativePtr); }; sCleaner.register(referent, releaser); @@ -79,10 +74,4 @@ public class NativeAllocationRegistry { 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 000000000000..ea6c9d4766b6 --- /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 000000000000..34cf9f915677 --- /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 000000000000..410292001670 --- /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 000000000000..3332e24ea013 --- /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 000000000000..b5038e68516d --- /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 b7f12adc90d4..c41a8cd78693 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_RE 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 @@ public class SaveEventLogger { 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 a8b123518db3..c46464b7cac8 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_HAS_EMP 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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 b414b252cc37..2d99c96452da 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 @@ public class FullRestoreEngine extends RestoreEngine { 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 78a9952d066d..4860a274cfa8 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_FULL_RESTORE_ 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.PackageInfo; 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 @@ public class TarBackupReader { 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 class TarBackupReader { */ 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 @@ public class TarBackupReader { 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 @@ public class TarBackupReader { 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 43fa0c2efc35..f7278e9f986d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -6025,46 +6025,47 @@ public class ActivityManagerService extends IActivityManager.Stub } 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; + } + } } } @@ -10306,7 +10307,8 @@ public class ActivityManagerService extends IActivityManager.Stub 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); diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java index 79a85182ac09..a8227fa8e38b 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 @@ public final class AppStartInfoTracker { 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 @@ public final class AppStartInfoTracker { } } + /** + * 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 @@ public final class AppStartInfoTracker { 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 @@ public final class AppStartInfoTracker { } 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 @@ public final class AppStartInfoTracker { 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; + } + } + if (succeeded) { + proto.flush(); + af.finishWrite(out); + } else { + af.failWrite(out); } - proto.flush(); - af.finishWrite(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 211f952551d9..db4840dc76c5 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.os.ProcLocksReader; 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.Map; 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 final class CachedAppOptimizer { 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 fa8832c845a5..219de709fb70 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.killProcessQuiet; 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 @@ public final class ProcessList { } } - 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/appop/AppOpsRecentAccessPersistence.java b/services/core/java/com/android/server/appop/AppOpsRecentAccessPersistence.java new file mode 100644 index 000000000000..238d9b968e88 --- /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 1acd3008915f..1bb792227798 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.ACTION_PACKAGE_REMOVED; 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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 @@ public class AppOpsService extends IAppOpsService.Stub { @VisibleForTesting final Constants mConstants; - @VisibleForTesting + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) final class UidState { public final int uid; @@ -642,7 +644,7 @@ public class AppOpsService extends IAppOpsService.Stub { } } - 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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 @@ public class AppOpsService extends IAppOpsService.Stub { if (!mRecentAccessesFile.exists()) { readRecentAccesses(mStorageFile); } else { - readRecentAccesses(mRecentAccessesFile); + if (deviceAwareAppOpNewSchemaEnabled()) { + synchronized (this) { + mRecentAccessPersistence.readRecentAccesses(mUidStates); + } + } else { + readRecentAccesses(mRecentAccessesFile); + } } } @@ -5091,6 +5100,14 @@ public class AppOpsService extends IAppOpsService.Stub { @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/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 7deef2ffb5dd..72c52544a44a 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 @@ public class AudioService extends IAudioService.Stub 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 30d12e670a6c..1949e6f2981e 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.EventLogTags; 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 @@ public class AutomaticBrightnessController { 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 @@ public class AutomaticBrightnessController { 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 @@ public class AutomaticBrightnessController { 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 @@ public class AutomaticBrightnessController { screenBrightnessThresholds, ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, context, brightnessModeController, brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux, - userNits, brightnessClamperController + userNits, brightnessClamperController, displayManagerFlags ); } @@ -320,9 +325,10 @@ public class AutomaticBrightnessController { 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 @@ public class AutomaticBrightnessController { mBrightnessClamperController = brightnessClamperController; mBrightnessThrottler = brightnessThrottler; mBrightnessMappingStrategyMap = brightnessMappingStrategyMap; + mDisplayManagerFlags = displayManagerFlags; // Use the given short-term model if (userNits != BrightnessMappingStrategy.INVALID_NITS) { @@ -429,34 +436,6 @@ public class AutomaticBrightnessController { 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 @@ public class AutomaticBrightnessController { mRecentLightSamples++; mAmbientLightRingBuffer.prune(time - mAmbientLightHorizonLong); mAmbientLightRingBuffer.push(time, lux); - // Remember this sample value. mLastObservedLux = lux; mLastObservedLuxTime = time; @@ -891,7 +869,7 @@ public class AutomaticBrightnessController { } private void updateAmbientLux() { - long time = mClock.uptimeMillis(); + long time = mClock.getSensorEventScaleTime(); mAmbientLightRingBuffer.prune(time - mAmbientLightHorizonLong); updateAmbientLux(time); } @@ -968,7 +946,16 @@ public class AutomaticBrightnessController { 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 @@ public class AutomaticBrightnessController { } 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 @@ public class AutomaticBrightnessController { resetShortTermModel(); mCurrentBrightnessMapper = mBrightnessMappingStrategyMap.get(mode); } + if (sendUpdate) { + update(); + } else { + updateAutoBrightness(/* sendUpdate= */ false, /* isManuallySet= */ false); + } } float getUserLux() { @@ -1391,7 +1381,9 @@ public class AutomaticBrightnessController { @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 @@ public class AutomaticBrightnessController { * 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 @@ public class AutomaticBrightnessController { 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 @@ public class AutomaticBrightnessController { } } + 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/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java index 65c9f3556fad..f77a360addd0 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 @@ public class DisplayOffloadSessionImpl implements DisplayManagerInternal.Display } @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 0fcdf198eece..7d482f74d5b3 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call @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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mDisplayStateController.shouldPerformScreenOffTransition()); state = mPowerState.getScreenState(); - DisplayBrightnessState displayBrightnessState = mDisplayBrightnessController .updateBrightness(mPowerRequest, state); float brightnessState = displayBrightnessState.getBrightness(); @@ -1366,17 +1366,26 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 b24caf4ced76..44c8d1cfee36 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 @@ class ExternalDisplayPolicy { handleExternalDisplayConnectedLocked(logicalDisplay); } } + if (!mDisplayIdsWaitingForBootCompletion.isEmpty()) { + mLogicalDisplayMapper.updateLogicalDisplaysLocked(); + } mDisplayIdsWaitingForBootCompletion.clear(); } @@ -222,7 +225,7 @@ class ExternalDisplayPolicy { } 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 01485cb3a4d1..e645e98c215c 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 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { 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 fc95d15ae6f4..9bf10a77d056 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 final class BrightnessReason { 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 @@ public final class BrightnessReason { 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 37b693190f7f..29073644ee2e 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 class AutomaticBrightnessStrategy extends AutomaticBrightnessStrategy2 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 @@ public class AutomaticBrightnessStrategy extends AutomaticBrightnessStrategy2 } /** - * 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 @@ public class AutomaticBrightnessStrategy extends AutomaticBrightnessStrategy2 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 58670c97e944..4d9c18ab72fe 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 @@ public class AutomaticBrightnessStrategy2 { 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 @@ public class AutomaticBrightnessStrategy2 { } /** - * 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 a5414fcf3970..8f775a54a8cd 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 @@ public class DisplayManagerFlags { 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 @@ public class DisplayManagerFlags { 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 @@ public class DisplayManagerFlags { 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 316b6db49954..697218dc0f41 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 { } 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 e20ac73dad98..76a2827050a9 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 @@ public class DisplayModeDirector { 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 f992a2399c61..88da6fb94754 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 @@ public class HdmiCecNetwork { // 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 @@ public class HdmiCecNetwork { // 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 class HdmiCecNetwork { } 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 cca73b5eabf0..dbd1e65e8b0f 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 @@ public class HdmiControlService extends SystemService { * 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 4ee2e99b824a..6da7a6500d54 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 @@ import java.util.concurrent.TimeUnit; @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 39df5be0f2fa..286e78951a03 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 @@ public class GnssLocationProvider extends AbstractLocationProvider implements 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 133704d11b08..6a72cc7c7779 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 @@ public class GnssManagerService { 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 dbc903d35cc8..ae79b010ca0f 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 @@ public class GnssMetrics { 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 @@ public class GnssMetrics { } @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 bdd488581817..c79a21a7eea8 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 @@ package com.android.server.location.gnss.hal; 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.GnssSignalType; 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.ElementType; 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 class GnssNative { 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 @@ public class GnssNative { 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 @@ public class GnssNative { @GuardedBy("GnssNative.class") private static GnssNative sInstance; + private final Handler mHandler; + /** * Sets GnssHal instance to use for testing. */ @@ -367,6 +387,14 @@ public class GnssNative { 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 @@ public class GnssNative { 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 @@ public class GnssNative { mGnssHal = Objects.requireNonNull(gnssHal); mEmergencyHelper = injector.getEmergencyHelper(); mConfiguration = configuration; + mHandler = FgThread.getHandler(); } public void addBaseCallbacks(BaseCallbacks callbacks) { @@ -532,8 +560,8 @@ public class GnssNative { /** * 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 @@ public class GnssNative { /** * 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 @@ public class GnssNative { @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/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java index 13429db47ebd..a7e14d9baea2 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 @@ public final class NotificationAttentionHelper { 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 44e76941c09b..c7b0f7dd3ad7 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 @@ public class NotificationManagerService extends SystemService { @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 @@ public class NotificationManagerService extends SystemService { } 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/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index db18276b1124..12e51809fabf 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { // 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { + " 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - 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/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java index e1b4b88ed1df..fbdba4f9206a 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 @@ package com.android.server.power.stats; 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 @@ class AggregatedPowerStats { 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 884c26ca3c00..1ff7cb773e68 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 @@ public class AggregatedPowerStatsConfig { 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 000000000000..64c3446d2cd7 --- /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 000000000000..490bd5e77c7b --- /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 4d6db9703ce7..077b05718503 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 @@ public class BluetoothPowerStatsProcessor extends PowerStatsProcessor { } @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 57b7259f9b56..6da0a8fef334 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 @@ public class CpuPowerStatsProcessor extends PowerStatsProcessor { } @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 eebed2f21946..dcce56283df2 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 @@ public class MobileRadioPowerStatsProcessor extends PowerStatsProcessor { } @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 a8222811c341..c3a0aeb12c08 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 @@ public class MultiStateStats { 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 @@ public class MultiStateStats { } /** + * 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 5c545fd073b2..ec23fa000f80 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 @@ public class PhoneCallPowerStatsProcessor extends PowerStatsProcessor { } @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 052873312d5c..85a229316b32 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 @@ class PowerComponentAggregatedPowerStats { 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 @@ class PowerComponentAggregatedPowerStats { } } - 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 @@ class PowerComponentAggregatedPowerStats { 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 @@ class PowerComponentAggregatedPowerStats { 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 6a4c1f0406a9..6e7fdf175179 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 @@ public class PowerStatsAggregator { 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 @@ public class PowerStatsAggregator { 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 @@ public class PowerStatsAggregator { 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 @@ public class PowerStatsAggregator { } if (lastTime > baseTime) { mStats.setDuration(lastTime - baseTime); - finish(mStats); + finish(mStats, lastTime); consumer.accept(mStats); } @@ -142,12 +159,22 @@ public class PowerStatsAggregator { } } - 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).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); + 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 58efd94bb82c..9624fd28bb2c 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 @@ public class PowerStatsLayout { 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 @@ public class PowerStatsLayout { private int mDeviceEnergyConsumerPosition; private int mDeviceEnergyConsumerCount; private int mDevicePowerEstimatePosition = UNSUPPORTED; + private int mUidDurationPosition = UNSUPPORTED; private int mUidPowerEstimatePosition = UNSUPPORTED; public PowerStatsLayout() { @@ -158,7 +160,8 @@ public class PowerStatsLayout { * PowerStatsService. */ public void addDeviceSectionEnergyConsumers(int energyConsumerCount) { - mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount, "energy"); + mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount, "energy", + FLAG_OPTIONAL); mDeviceEnergyConsumerCount = energyConsumerCount; } @@ -206,6 +209,13 @@ public class PowerStatsLayout { } /** + * 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 @@ public class PowerStatsLayout { } /** + * 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 @@ public class PowerStatsLayout { 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 @@ public class PowerStatsLayout { 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 2fd0b9a9b001..f257e1a3a1e3 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 @@ import java.util.List; 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 @@ abstract class PowerStatsProcessor { } 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 @@ abstract class PowerStatsProcessor { } 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 @@ abstract class PowerStatsProcessor { } } - 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 a4a2e183f86b..4e035c31f3c5 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 @@ public class WifiPowerStatsProcessor extends PowerStatsProcessor { } @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 f62c76e1505a..e00e81371853 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.AlarmManager.OnAlarmListener; 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.BiometricSourceType; 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.infra.AndroidFuture; 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { } } + @Override + public boolean isInSignificantPlace() { + return mIsInSignificantPlace; + } + private void enforceReportPermission() { mContext.enforceCallingOrSelfPermission( Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE, "reporting trust events"); @@ -1680,6 +1725,9 @@ public class TrustManagerService extends SystemService { 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 110100ac735b..8d14c1d64b9c 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.Context; 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 @@ public class WearableSensingManagerService extends @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 af5956786c0a..eb6262c95d84 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.pm.UserProperties; 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.RemoteAnimationTarget; 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 class ActivityRecord extends WindowToken implements WindowManagerService.A } 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return mLetterboxBoundsForFixedOrientationAndAspectRatio != null; } + boolean isLetterboxedForAspectRatioOnly() { + return mLetterboxBoundsForAspectRatio != null; + } + boolean isAspectRatioApplied() { return mIsAspectRatioApplied; } @@ -9235,11 +9266,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // 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 2cccd33d0884..1a9d21187ddb 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.RemoteException; 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 @@ import com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptResult * 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 @@ class ActivityStartInterceptor { 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 f06d3af49768..a10f7e7b00d0 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 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } 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 a9450c4e8587..eb85c1a73212 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 @@ class AsyncRotationController extends FadeAnimationController implements Consume */ 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 1128328a26b1..50ac801a9d04 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 @@ package com.android.server.wm; 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 84dadab7da59..d0086aa24337 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.ScreenOnListener; 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 @@ public class DisplayPolicy { 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 db54647b4b40..000000000000 --- 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 cd071af6c60e..9416daf48e8b 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_ATM; 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 @@ class LaunchParamsUtil { } 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 6e11e082cc07..b8d0694047c7 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 @@ final class LetterboxUiController { 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 3606a34e23e0..69be0d94f243 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 @@ class PerfettoTransitionTracer implements TransitionTracer { 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 100735784fb1..b7944d3b8234 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 @@ class SystemGesturesPointerEventListener implements PointerEventListener { 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 f37638f92137..5c9a84db002a 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 android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; 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 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { 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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index d733762e90e5..e763c9eccceb 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.File; 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 @@ final class DevicePolicyEngine { 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 @@ final class DevicePolicyEngine { 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 @@ final class DevicePolicyEngine { } // 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 @@ final class DevicePolicyEngine { */ @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,8 +706,36 @@ final class DevicePolicyEngine { 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(); } /** @@ -1743,6 +1778,18 @@ final class DevicePolicyEngine { } } + /** + * 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 @@ final class DevicePolicyEngine { 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 @@ final class DevicePolicyEngine { 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 5bf5efda0fd2..bdd073044c45 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.LinkedBlockingQueue; 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + /** + * @deprecated use {@link #getResolvedLockscreenPolicy(PolicyDefinition, int)} for + * coexistable policies + */ @GuardedBy("getLockObject()") private List<ActiveAdmin> getActiveAdminsForLockscreenPoliciesLocked(int userHandle) { if (isSeparateProfileChallengeEnabled(userHandle)) { @@ -4428,6 +4503,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } /** + * 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /* 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + // 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { }); } + @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 7e8eaa76431b..20663769d79f 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 @@ package com.android.server.devicepolicy; 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 @@ final class MostRestrictive<V> extends ResolutionMechanism<V> { @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 7912cbce554f..3f9605ac2e5d 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 @@ class Owners { } } + 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 d02cfee72aa2..2ea5f168bdd1 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 @@ class OwnersData { 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 @@ class OwnersData { boolean mMigratedToPolicyEngine = false; boolean mSecurityLoggingMigrated = false; + boolean mRequiredPasswordComplexityMigrated = false; + boolean mSuspendedPackagesMigrated = false; boolean mPoliciesMigratedPostUpdate = false; @@ -409,6 +413,14 @@ class OwnersData { 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 @@ class OwnersData { 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 000000000000..40cf0e979375 --- /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 8bec3847d8ca..a0ea4e9b94f4 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 @@ final class PolicyDefinition<V> { 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 @@ final class PolicyDefinition<V> { 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 @@ final class PolicyDefinition<V> { 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 @@ final class PolicyDefinition<V> { 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 @@ final class PolicyDefinition<V> { 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 @@ final class PolicyDefinition<V> { 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 @@ final class PolicyDefinition<V> { 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 @@ final class PolicyDefinition<V> { 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 04d277e6e667..e1cb37dbeef5 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 @@ final class PolicyEnforcerCallbacks { 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 c544ebc008b9..245c43884e02 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 @@ final class PolicyState<V> { 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 c321aa1ef89e..ad7ac2bd3f18 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.annotation.Nullable; 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 cfe4e17eb1be..927df8bb23e7 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -761,6 +761,9 @@ public final class SystemServer implements Dumpable { } } + 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 @@ public final class SystemServer implements Dumpable { 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 3628a57f2375..d3efcb6e50b3 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.SensorManager; 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 androidx.test.runner.AndroidJUnit4; 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 @@ import org.mockito.MockitoAnnotations; @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 @@ public class AutomaticBrightnessControllerTest { @Mock BrightnessRangeController mBrightnessRangeController; @Mock BrightnessClamperController mBrightnessClamperController; + @Mock + DisplayManagerFlags mDisplayManagerFlags; @Mock BrightnessThrottler mBrightnessThrottler; @Before @@ -148,8 +153,18 @@ public class AutomaticBrightnessControllerTest { } @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 @@ public class AutomaticBrightnessControllerTest { 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 @@ public class AutomaticBrightnessControllerTest { 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 @@ public class AutomaticBrightnessControllerTest { 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 @@ public class AutomaticBrightnessControllerTest { 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 @@ public class AutomaticBrightnessControllerTest { 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 @@ public class AutomaticBrightnessControllerTest { 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 @@ public class AutomaticBrightnessControllerTest { 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 @@ public class AutomaticBrightnessControllerTest { // 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 @@ public class AutomaticBrightnessControllerTest { // 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 @@ public class AutomaticBrightnessControllerTest { // 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 @@ public class AutomaticBrightnessControllerTest { 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 @@ public class AutomaticBrightnessControllerTest { } @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 @@ public class AutomaticBrightnessControllerTest { 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 @@ public class AutomaticBrightnessControllerTest { 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,7 +1082,7 @@ public class AutomaticBrightnessControllerTest { } @Test - public void testBrightnessBasedOnLastUsedLux() throws Exception { + public void testAutoBrightnessInDoze() throws Exception { ArgumentCaptor<SensorEventListener> listenerCaptor = ArgumentCaptor.forClass(SensorEventListener.class); verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), @@ -1047,19 +1099,23 @@ public class AutomaticBrightnessControllerTest { /* 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, + // Set policy to DOZE + mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null, /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0, - /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT, Display.STATE_ON, + /* userChanged= */ false, DisplayPowerRequest.POLICY_DOZE, Display.STATE_DOZE, /* shouldResetShortTermModel= */ true); - assertEquals(normalizedBrightness, - mController.getAutomaticScreenBrightnessBasedOnLastUsedLux( + + // Send a new sensor value + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux)); + + // The brightness should be scaled by the doze factor + assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR, + mController.getAutomaticScreenBrightness( /* brightnessEvent= */ null), EPSILON); } @Test - public void testAutoBrightnessInDoze() throws Exception { + public void testAutoBrightnessInDoze_ShouldNotScaleIfUsingDozeCurve() throws Exception { ArgumentCaptor<SensorEventListener> listenerCaptor = ArgumentCaptor.forClass(SensorEventListener.class); verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), @@ -1072,10 +1128,13 @@ public class AutomaticBrightnessControllerTest { float normalizedBrightness = 0.3f; when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux); when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux); - when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null), + when(mDozeBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null), /* category= */ anyInt())).thenReturn(normalizedBrightness); when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT); + // Switch mode to DOZE + mController.switchMode(AUTO_BRIGHTNESS_MODE_DOZE, /* sendUpdate= */ false); + // Set policy to DOZE mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null, /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0, @@ -1085,17 +1144,13 @@ public class AutomaticBrightnessControllerTest { // Send a new sensor value listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux)); - // The brightness should be scaled by the doze factor - assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR, - mController.getAutomaticScreenBrightness( - /* brightnessEvent= */ null), EPSILON); - assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR, - mController.getAutomaticScreenBrightnessBasedOnLastUsedLux( - /* brightnessEvent= */ null), EPSILON); + // The brightness should not be scaled by the doze factor + assertEquals(normalizedBrightness, + mController.getAutomaticScreenBrightness(/* brightnessEvent= */ null), EPSILON); } @Test - public void testAutoBrightnessInDoze_ShouldNotScaleIfUsingDozeCurve() throws Exception { + public void testAutoBrightnessInDoze_ShouldNotScaleIfScreenOn() throws Exception { ArgumentCaptor<SensorEventListener> listenerCaptor = ArgumentCaptor.forClass(SensorEventListener.class); verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), @@ -1108,17 +1163,14 @@ public class AutomaticBrightnessControllerTest { float normalizedBrightness = 0.3f; when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux); when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux); - when(mDozeBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null), + when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null), /* category= */ anyInt())).thenReturn(normalizedBrightness); when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT); - // Switch mode to DOZE - mController.switchMode(AUTO_BRIGHTNESS_MODE_DOZE); - // Set policy to DOZE mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null, /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0, - /* userChanged= */ false, DisplayPowerRequest.POLICY_DOZE, Display.STATE_DOZE, + /* userChanged= */ false, DisplayPowerRequest.POLICY_DOZE, Display.STATE_ON, /* shouldResetShortTermModel= */ true); // Send a new sensor value @@ -1127,13 +1179,10 @@ public class AutomaticBrightnessControllerTest { // 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 - public void testAutoBrightnessInDoze_ShouldNotScaleIfScreenOn() throws Exception { + public void testSwitchMode_UpdateBrightnessImmediately() throws Exception { ArgumentCaptor<SensorEventListener> listenerCaptor = ArgumentCaptor.forClass(SensorEventListener.class); verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), @@ -1146,24 +1195,47 @@ public class AutomaticBrightnessControllerTest { float normalizedBrightness = 0.3f; when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux); when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux); - when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null), + when(mDozeBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null), /* category= */ anyInt())).thenReturn(normalizedBrightness); when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT); - // Set policy to DOZE - mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null, - /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0, - /* userChanged= */ false, DisplayPowerRequest.POLICY_DOZE, Display.STATE_ON, - /* shouldResetShortTermModel= */ true); - // Send a new sensor value listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux)); - // The brightness should not be scaled by the doze factor + // Switch mode to DOZE + mController.switchMode(AUTO_BRIGHTNESS_MODE_DOZE, /* sendUpdate= */ false); + assertEquals(normalizedBrightness, 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.getAutomaticScreenBrightnessBasedOnLastUsedLux( - /* brightnessEvent= */ null), EPSILON); + 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 8844e6cc3a2c..d0eb83aa986b 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 @@ public class DisplayManagerServiceTest { 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 @@ public class DisplayManagerServiceTest { } 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 e5685c7f4f43..98f572d81adf 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 @@ public final class DisplayPowerControllerTest { } @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 @@ public final class DisplayPowerControllerTest { } @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 @@ public final class DisplayPowerControllerTest { } @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 @@ public final class DisplayPowerControllerTest { /* ambientLightHorizonLong= */ anyInt(), eq(lux), eq(nits), - any(BrightnessClamperController.class) + any(BrightnessClamperController.class), + any(DisplayManagerFlags.class) ); } @@ -1668,7 +1729,8 @@ public final class DisplayPowerControllerTest { 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 @@ public final class DisplayPowerControllerTest { 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 @@ public final class DisplayPowerControllerTest { 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 @@ public final class DisplayPowerControllerTest { advanceTime(1); // Run updatePowerState verify(mHolder.automaticBrightnessController, never()) - .switchMode(AUTO_BRIGHTNESS_MODE_DOZE); + .switchMode(eq(AUTO_BRIGHTNESS_MODE_DOZE), /* sendUpdate= */ anyBoolean()); } @Test @@ -1764,20 +1827,15 @@ public final class DisplayPowerControllerTest { } @Test - public void testInitialDozeBrightness_AutoBrightnessEnabled() { - when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true); + public void testDozeManualBrightness() { 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); + 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); @@ -1785,17 +1843,27 @@ public final class DisplayPowerControllerTest { DisplayPowerRequest dpr = new DisplayPowerRequest(); dpr.policy = DisplayPowerRequest.POLICY_DOZE; mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); - advanceTime(1); // Run updatePowerState + advanceTime(1); // Run updatePowerState, initialize - verify(mHolder.animator).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()); - verify(mHolder.brightnessSetting).setBrightness(brightness); + assertEquals(brightness * DOZE_SCALE_FACTOR, mHolder.dpc.getDozeBrightnessForOffload(), + /* delta= */ 0); } @Test - public void testInitialDozeBrightness_AutoBrightnessDisabled() { + public void testDozeManualBrightness_AbcIsNull() { when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true); + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true, + /* isAutoBrightnessAvailable= */ false); mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); Settings.System.putInt(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, @@ -1827,22 +1895,16 @@ public final class DisplayPowerControllerTest { } @Test - public void testInitialDozeBrightness_AbcIsNull() { - when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true); - when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true); - Settings.System.putInt(mContext.getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS_MODE, - Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + public void testDefaultDozeBrightness() { + 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, /* isEnabled= */ true, - /* isAutoBrightnessAvailable= */ false); - mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); - float brightness = 0.277f; + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); - when(mHolder.automaticBrightnessController - .getAutomaticScreenBrightnessBasedOnLastUsedLux(any(BrightnessEvent.class))) - .thenReturn(brightness); + 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); @@ -1852,18 +1914,17 @@ public final class DisplayPowerControllerTest { mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); advanceTime(1); // Run updatePowerState - // 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), - /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(), - /* ignoreAnimationLimits= */ anyBoolean()); + verify(mHolder.animator).animateTo(eq(brightness), /* linearSecondTarget= */ anyFloat(), + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); } @Test - public void testDefaultDozeBrightness() { + 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( @@ -1877,8 +1938,9 @@ public final class DisplayPowerControllerTest { mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); advanceTime(1); // Run updatePowerState - verify(mHolder.animator).animateTo(eq(brightness), /* linearSecondTarget= */ anyFloat(), - eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); + verify(mHolder.animator, never()).animateTo(eq(brightness), + /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(), + /* ignoreAnimationLimits= */ anyBoolean()); } /** @@ -2186,7 +2248,8 @@ public final class DisplayPowerControllerTest { 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 ea08be4f1be4..82acaf86988d 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 @@ public class ExternalDisplayPolicyTest { 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 7fd96c57c215..12050e1beaed 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 @@ public class LocalDisplayAdapterTest { @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 8b45145b160f..18dfcc1afd01 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 @@ import java.lang.reflect.Method; 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 09f5bb60a6ad..498bffd260dd 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 @@ public class AutomaticBrightnessStrategy2Test { 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 d19f47951df1..afb5a5cc5cc4 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 @@ package com.android.server.display.brightness.strategy; 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 @@ public class AutomaticBrightnessStrategyTest { 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 @@ public class AutomaticBrightnessStrategyTest { 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 @@ public class AutomaticBrightnessStrategyTest { 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 @@ public class AutomaticBrightnessStrategyTest { 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 33995653870e..396f4da75172 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_RESTORE_ANY_V 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.PackageManagerInternal; 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.android.server.backup.testutils.PackageManagerStub; 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 @@ public class TarBackupReaderTest { @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 class TarBackupReaderTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mContext = InstrumentationRegistry.getContext(); + mContext = new TestableContext(ApplicationProvider.getApplicationContext()); mUserId = UserHandle.USER_SYSTEM; } @@ -515,6 +525,107 @@ public class TarBackupReaderTest { @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 @@ public class TarBackupReaderTest { 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 @@ public class TarBackupReaderTest { 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 @@ public class TarBackupReaderTest { 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 000000000000..a14bcaf39fcf --- /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 000000000000..be1c121cfb29 --- /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 752bc2712fe2..c88f0a9d11e1 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 @@ public class BluetoothPowerStatsProcessorTest { aggregatedStats.addPowerStats(collector.collectStats(), 10_000); - processor.finish(aggregatedStats); + processor.finish(aggregatedStats, 10_000); BluetoothPowerStatsLayout statsLayout = new BluetoothPowerStatsLayout(aggregatedStats.getPowerStatsDescriptor()); @@ -301,7 +301,7 @@ public class BluetoothPowerStatsProcessorTest { aggregatedStats.addPowerStats(collector.collectStats(), 10_000); - processor.finish(aggregatedStats); + processor.finish(aggregatedStats, 10_000); BluetoothPowerStatsLayout statsLayout = new BluetoothPowerStatsLayout(aggregatedStats.getPowerStatsDescriptor()); @@ -408,7 +408,7 @@ public class BluetoothPowerStatsProcessorTest { 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 6b5da81954d0..b6b759e8f0af 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 @@ public class CpuPowerStatsProcessorTest { 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 @@ public class CpuPowerStatsProcessorTest { 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 29ef3b6551a6..137c2a6a36d9 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 @@ public class MobileRadioPowerStatsProcessorTest { aggregatedStats.addPowerStats(powerStats, 10_000); - processor.finish(aggregatedStats); + processor.finish(aggregatedStats, 10_000); MobileRadioPowerStatsLayout statsLayout = new MobileRadioPowerStatsLayout( @@ -475,7 +475,7 @@ public class MobileRadioPowerStatsProcessorTest { 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 69d655bc35c0..548d54cc3efc 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 @@ public class PhoneCallPowerStatsProcessorTest { 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 3ceaf357150e..ff566919b7a3 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 @@ public class WifiPowerStatsProcessorTest { aggregatedStats.addPowerStats(collector.collectStats(), 10_000); - processor.finish(aggregatedStats); + processor.finish(aggregatedStats, 10_000); WifiPowerStatsLayout statsLayout = new WifiPowerStatsLayout(aggregatedStats.getPowerStatsDescriptor()); @@ -355,7 +355,7 @@ public class WifiPowerStatsProcessorTest { aggregatedStats.addPowerStats(collector.collectStats(), 10_000); - processor.finish(aggregatedStats); + processor.finish(aggregatedStats, 10_000); WifiPowerStatsLayout statsLayout = new WifiPowerStatsLayout(aggregatedStats.getPowerStatsDescriptor()); @@ -454,7 +454,7 @@ public class WifiPowerStatsProcessorTest { 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 000000000000..5aceea3530ad --- /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 000000000000..c4b3c149bd8d --- /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 24704034ae0c..e72d9e76e4e3 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { .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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 c1ae85252ffc..6d863015231c 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 @@ public class ActiveSourceActionTest { 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 @@ public class ActiveSourceActionTest { 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 e669e7c019d6..2296911a4e7e 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 @@ public class DevicePowerStatusActionTest { 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 @@ public class DevicePowerStatusActionTest { 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 29d20a64e439..47cfa4218435 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 @@ public class DeviceSelectActionFromPlaybackTest { 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 @@ public class DeviceSelectActionFromPlaybackTest { 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 d32b75bc57da..eb4a628e14e5 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 @@ public class DeviceSelectActionFromTvTest { 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 @@ public class DeviceSelectActionFromTvTest { 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 cb19029d246e..bfe435c2b30b 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 @@ final class FakeNativeWrapper implements NativeWrapper { 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 @@ final class FakeNativeWrapper implements NativeWrapper { @Override public int nativeGetPhysicalAddress() { + mGetPhysicalAddressCalled = true; return mMyPhysicalAddress; } @@ -161,6 +163,10 @@ final class FakeNativeWrapper implements NativeWrapper { return mIsCecControlEnabled; } + public boolean getPhysicalAddressCalled() { + return mGetPhysicalAddressCalled; + } + public void setCecVersion(@HdmiControlManager.HdmiCecVersion int cecVersion) { mCecVersion = cecVersion; } @@ -200,6 +206,10 @@ final class FakeNativeWrapper implements NativeWrapper { 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 5502de8f46e9..02441648a589 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 @@ public class HdmiCecLocalDeviceAudioSystemTest { 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 @@ public class HdmiCecLocalDeviceAudioSystemTest { 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 8df7d548e21e..95a7f4b6c80f 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 @@ public class HdmiCecLocalDevicePlaybackTest { } }; mHdmiCecLocalDevicePlayback.init(); + mPlaybackPhysicalAddress = 0x2000; HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; hdmiPortInfos[0] = new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_OUTPUT, 0x0000) @@ -176,13 +177,12 @@ public class HdmiCecLocalDevicePlaybackTest { .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 @@ public class HdmiCecLocalDevicePlaybackTest { 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 192be2a4200b..004c6c68781c 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 @@ public class HdmiCecLocalDeviceTest { 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 @@ public class HdmiCecLocalDeviceTest { 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 @@ public class HdmiCecLocalDeviceTest { @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 @@ public class HdmiCecLocalDeviceTest { @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 b50684bb7a25..2a4b79730851 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 @@ public class HdmiCecLocalDeviceTvTest { .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 1ad9ce02daa3..10f4308cbcfb 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 @@ public class HdmiCecNetworkTest { 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 9412ee0d4ac7..3361e7f359e2 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 @@ public class HdmiCecPowerStatusControllerTest { .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 298ff460c9eb..2f4a6604441f 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 @@ public class OneTouchPlayActionTest { 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 @@ public class OneTouchPlayActionTest { 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 1d4a72fc30e2..974f64dbd84f 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 @@ public class PowerStatusMonitorActionTest { .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 cafe1e7dc197..f8e465c4c36f 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 @@ public class RequestSadActionTest { 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 @@ public class RequestSadActionTest { 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 864a182e8d0d..67a3f2a64d32 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 @@ public class ResendCecCommandActionPlaybackTest { 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 @@ public class ResendCecCommandActionPlaybackTest { 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 06709cdd6ac4..047a04c60176 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 @@ public class ResendCecCommandActionTvTest { 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 @@ public class ResendCecCommandActionTvTest { 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 5163e29b86f1..1019db46482d 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 @@ public class RoutingControlActionTest { mHdmiControlService.setIoLooper(mMyLooper); mNativeWrapper = new FakeNativeWrapper(); + mNativeWrapper.setPhysicalAddress(0x0000); mHdmiCecController = HdmiCecController.createWithNativeWrapper( mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper()); @@ -205,7 +206,6 @@ public class RoutingControlActionTest { 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 4dcc6a400c1e..effea5abcecb 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 @@ public class SystemAudioAutoInitiationActionTest { 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 @@ public class SystemAudioAutoInitiationActionTest { 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/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java index 70a003814036..3da80314e6d4 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 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { 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 e564ba6eb835..15c9bfb74d92 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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 fd69217c9778..2e571bb9eceb 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.DOUBLE_TAP_HOME_RECEN 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 @@ public class ShortcutLoggingTests extends ShortcutKeyTestBase { } @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 @@ public class ShortcutLoggingTests extends ShortcutKeyTestBase { 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 @@ public class ShortcutLoggingTests extends ShortcutKeyTestBase { } @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 dd9d05aec8c5..fdb57d1e29d1 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 @@ class TestPhoneWindowManager { 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 12c13771e820..a4bec64bdf97 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 @@ import org.junit.runner.RunWith; @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 55f5df104824..87671f2ae8af 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.LaunchParams; 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 @@ public class LaunchParamsModifierTestsBase extends WindowTestsBase{ ActivityRecord mActivity; - LaunchParamsModifier mTarget; + T mTarget; LaunchParams mCurrent; LaunchParams mResult; 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 6b605ec6d0c0..96ddfe8d5ac9 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.Binder; 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 @@ public class SizeCompatTests extends WindowTestsBase { 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 @@ public class SizeCompatTests extends WindowTestsBase { 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 @@ public class SizeCompatTests extends WindowTestsBase { // 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 2ce21ba715bf..3c921c612705 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 @@ import java.util.Locale; @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 @@ public class TaskLaunchParamsModifierTests extends LaunchParamsModifierTestsBase } 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/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 379b45cdf08e..a23f211ea1d2 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.FlickerBuilder 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 @@ class EnterSystemSplitTest(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBa } } + @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 000000000000..bf569bc23df6 --- /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 3e500d9c8bd4..45260bddd355 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 4c1fa6ec40b3..4995eebdd79e 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.SourceVersion 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 @@ class IntDefProcessor : AbstractProcessor() { @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 c0c159c98aac..d87b6df901b2 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 junit.framework.Assert.assertEquals 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 @@ class IntDefProcessorTest { .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) } |